diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..5f2656813a --- /dev/null +++ b/.clang-format @@ -0,0 +1,128 @@ +# Checkout config tool: https://zed0.co.uk/clang-format-configurator/ +# Or http://cf.monofraps.net/ +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# https://github.com/01org/parameter-framework/blob/master/.clang-format + +# Tested on: clang-format version 6.0.1 + + +# Common settings +BasedOnStyle: WebKit +TabWidth: 4 +IndentWidth: 4 +UseTab: Always +ColumnLimit: 120 + +# Other languages JavaScript, Proto + +--- +Language: Cpp + +# http://releases.llvm.org/6.0.1/tools/clang/docs/ClangFormatStyleOptions.html#disabling-formatting-on-a-piece-of-code +# int formatted_code; +# // clang-format off +# void unformatted_code ; +# // clang-format on +# void formatted_code_again; + +DisableFormat: false +Standard: Cpp11 + +AccessModifierOffset: -4 +AlignAfterOpenBracket: true +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false + +# Configure each individual brace in BraceWrapping +BreakBeforeBraces: Custom +# Control of individual brace wrapping cases +BraceWrapping: { + AfterClass: 'true' + AfterControlStatement: 'true' + AfterEnum : 'true' + AfterFunction : 'true' + AfterNamespace : 'true' + AfterStruct : 'true' + AfterUnion : 'true' + BeforeCatch : 'true' + BeforeElse : 'true' + IndentBraces : 'false' + AfterExternBlock : 'true' + SplitEmptyFunction : 'false' + SplitEmptyRecord : 'false' + SplitEmptyNamespace : 'true' +} + +BreakAfterJavaFieldAnnotations: true +BreakBeforeInheritanceComma: false +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BreakStringLiterals: true + +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +SpaceBeforeCpp11BracedList: false +DerivePointerAlignment: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IndentCaseLabels: false +FixNamespaceComments: true +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +JavaScriptQuotes: Double +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 + +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Never +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceAfterTemplateKeyword: true +SpaceBeforeInheritanceColon: true + +SortUsingDeclarations: true +SortIncludes: true + +# Comments are for developers, they should arrange them +ReflowComments: false + +IncludeBlocks: Preserve +IndentPPDirectives: AfterHash diff --git a/.gitignore b/.gitignore index 81ea2aa1c8..80c5b62156 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,57 @@ status-react-translations/ /.update.timestamp notarization.log status-desktop.log -nim_status_client.log \ No newline at end of file +nim_status_client.log + + +# CPP app ===================================================================== + + +# https://github.com/github/gitignore/blob/master/CMake.gitignore +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + + + +# https://github.com/github/gitignore/blob/master/C%2B%2B.gitignore +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..12c8ad2773 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,135 @@ +cmake_minimum_required(VERSION 3.14) + +project(status-desktop + VERSION 0.1.0 + LANGUAGES CXX) + +if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + message( + FATAL_ERROR + "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there." + ) +endif() + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +message(STATUS "Started CMake for ${PROJECT_NAME} v${PROJECT_VERSION}...\n") + +include(ExternalProject) + +# QtCreator supports the following variables for Android, which are identical to qmake Android variables. +# Check https://doc.qt.io/qt/deployment-android.html for more information. +# They need to be set before the find_package(...) calls below. + +#if(ANDROID) +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") +# if (ANDROID_ABI STREQUAL "armeabi-v7a") +# set(ANDROID_EXTRA_LIBS +# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so +# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so) +# endif() +#endif() + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick Widgets Concurrent REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick Widgets Concurrent REQUIRED) + +set(PROJECT_SOURCES + src-cpp/main.cpp + src-cpp/constants.cpp + src-cpp/logs.cpp + resources.rcc +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(status-desktop + ${PROJECT_SOURCES} + ) +else() + if(ANDROID) + add_library(status-desktop SHARED + ${PROJECT_SOURCES} + ) + else() + add_executable(status-desktop + ${PROJECT_SOURCES} + ) + endif() +endif() + + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/vendor/status-lib/vendor/status-go bin/status-go) + +# The following are dependencies that do not include CMakeList.txt files +# Create a PR in those projects so we can include them just like it was +# done with status-go +include(cmake/QRCodeGen.cmake) +include(cmake/Keycard.cmake) + +# Submodiles +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src-cpp/dotherside) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src-cpp/app) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src-cpp/app_service) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src-cpp/backend) + +target_include_directories(status-desktop PUBLIC include) + + +target_compile_definitions(status-desktop + PRIVATE $<$,$>:QT_QML_DEBUG>) + +target_link_libraries(status-desktop + PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::Widgets + DOtherSide + statusgo_shared # <- Link status-go with this target (remove `_shared` for static linking) + qrcodegen + keycard + app_service + app + ) + + +add_custom_target(rcc ALL DEPENDS resources.rcc) +add_custom_command( + OUTPUT resources.rcc + COMMENT "Building resources.rcc" + COMMAND ${CMAKE_COMMAND} -E rm -f ${CMAKE_CURRENT_SOURCE_DIR}/resources.rcc + COMMAND ${CMAKE_COMMAND} -E rm -f ${CMAKE_CURRENT_SOURCE_DIR}/ui/resources.qrc + COMMAND go run ${CMAKE_CURRENT_SOURCE_DIR}/ui/generate-rcc.go -source=${CMAKE_CURRENT_SOURCE_DIR}/ui -output=${CMAKE_CURRENT_SOURCE_DIR}/ui/resources.qrc + COMMAND rcc -binary $<$:--no-compress> ${CMAKE_CURRENT_SOURCE_DIR}/ui/resources.qrc ${CMAKE_CURRENT_SOURCE_DIR}/resources/resources.qrc -o ${CMAKE_CURRENT_SOURCE_DIR}/resources.rcc + VERBATIM + USES_TERMINAL +) + + +# run +add_custom_target(run + COMMENT "Execute desktop" + DEPENDS status-desktop + COMMAND ./status-desktop + VERBATIM + USES_TERMINAL +) + + +# run debug +add_custom_target(run-debug + COMMENT "Execute desktop with debugger on port 1234" + DEPENDS status-desktop + COMMAND ./status-desktop -qmljsdebugger=port:1234 + VERBATIM + USES_TERMINAL +) diff --git a/README.md b/README.md index e486cf8191..0c08510571 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,15 @@ Desktop client for the [Status Network](https://statusnetwork.com/) built with [ Dev Docs: [https://hackmd.io/@status-desktop/B1naRjxh_/https%3A%2F%2Fhackmd.io%2F%40status-desktop%2FB1eOaf-nd](https://hackmd.io/@status-desktop/B1naRjxh_/https%3A%2F%2Fhackmd.io%2F%40status-desktop%2FB1eOaf-nd) + + + + +# CPP app + +``` +cd build +cmake .. +make +./test-qtapp +``` \ No newline at end of file diff --git a/qt-example-app/build/.gitignore b/build/.gitignore similarity index 100% rename from qt-example-app/build/.gitignore rename to build/.gitignore diff --git a/cmake/Keycard.cmake b/cmake/Keycard.cmake new file mode 100644 index 0000000000..6073ff9111 --- /dev/null +++ b/cmake/Keycard.cmake @@ -0,0 +1,20 @@ +# Keycard +# TODO: create a PR in that project to build it like we do with status-go ^ +set(KEYCARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/vendor/status-lib/vendor/nim-keycard-go) +set(KEYCARD_LIB_DIR ${KEYCARD_ROOT}/go/keycard/build/libkeycard) +ExternalProject_Add(libkeycard + PREFIX ${KEYCARD_ROOT} + SOURCE_DIR ${KEYCARD_ROOT} + UPDATE_COMMAND "" + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + BUILD_IN_SOURCE 1 + BUILD_COMMAND make build-keycard-go V=1 + BUILD_BYPRODUCTS ${KEYCARD_LIB_DIR}/libkeycard${CMAKE_SHARED_LIBRARY_SUFFIX} +) +ExternalProject_Get_Property(libkeycard SOURCE_DIR) +add_library(keycard SHARED IMPORTED) +set_property(TARGET keycard PROPERTY IMPORTED_LOCATION ${KEYCARD_LIB_DIR}/libkeycard${CMAKE_SHARED_LIBRARY_SUFFIX}) +add_dependencies(keycard libkeycard) +include_directories(${KEYCARD_LIB_DIR}) diff --git a/cmake/QRCodeGen.cmake b/cmake/QRCodeGen.cmake new file mode 100644 index 0000000000..e4c1cb7c9e --- /dev/null +++ b/cmake/QRCodeGen.cmake @@ -0,0 +1,20 @@ +# QR-Code-generator +# TODO: create a PR in that project to build it like we do with status-go ^ +set(QRCODE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/vendor/QR-Code-generator) +set(QRCODE_LIB_DIR ${QRCODE_ROOT}/cpp) +ExternalProject_Add(libqrcodegen + PREFIX ${QRCODE_ROOT} + SOURCE_DIR ${QRCODE_ROOT} + UPDATE_COMMAND "" + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + BUILD_IN_SOURCE 1 + BUILD_COMMAND make -C cpp libqrcodegen.a V=1 + BUILD_BYPRODUCTS ${QRCODE_LIB_DIR}/libqrcodegen.a +) +ExternalProject_Get_Property(libqrcodegen SOURCE_DIR) +add_library(qrcodegen STATIC IMPORTED) +set_property(TARGET qrcodegen PROPERTY IMPORTED_LOCATION ${QRCODE_LIB_DIR}/libqrcodegen.a) +add_dependencies(qrcodegen libqrcodegen) +include_directories(${QRCODE_LIB_DIR}) diff --git a/qt-example-app/.gitignore b/qt-example-app/.gitignore deleted file mode 100644 index 5f304636f3..0000000000 --- a/qt-example-app/.gitignore +++ /dev/null @@ -1,53 +0,0 @@ - -# https://github.com/github/gitignore/blob/master/CMake.gitignore -# ============================================================================== - -CMakeLists.txt.user -CMakeCache.txt -CMakeFiles -CMakeScripts -Testing -Makefile -cmake_install.cmake -install_manifest.txt -compile_commands.json -CTestTestfile.cmake -_deps - - - -# https://github.com/github/gitignore/blob/master/C%2B%2B.gitignore -# ============================================================================== - -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app diff --git a/qt-example-app/CMakeLists.txt b/qt-example-app/CMakeLists.txt deleted file mode 100644 index 41cb2eb1f7..0000000000 --- a/qt-example-app/CMakeLists.txt +++ /dev/null @@ -1,63 +0,0 @@ -cmake_minimum_required(VERSION 3.14) - -project(test-qtapp LANGUAGES CXX) - -set(CMAKE_INCLUDE_CURRENT_DIR ON) - -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -include(ExternalProject) - -# QtCreator supports the following variables for Android, which are identical to qmake Android variables. -# Check https://doc.qt.io/qt/deployment-android.html for more information. -# They need to be set before the find_package(...) calls below. - -#if(ANDROID) -# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") -# if (ANDROID_ABI STREQUAL "armeabi-v7a") -# set(ANDROID_EXTRA_LIBS -# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so -# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so) -# endif() -#endif() - -find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick REQUIRED) -find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick REQUIRED) - -set(PROJECT_SOURCES - main.cpp - qml.qrc -) - -if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) - qt_add_executable(test-qtapp - ${PROJECT_SOURCES} - ) -else() - if(ANDROID) - add_library(test-qtapp SHARED - ${PROJECT_SOURCES} - ) - else() - add_executable(test-qtapp - ${PROJECT_SOURCES} - ) - endif() -endif() - -# status-go has a CMakeLists.txt file we can use -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../vendor/status-lib/vendor/status-go bin/status-go) - -target_compile_definitions(test-qtapp - PRIVATE $<$,$>:QT_QML_DEBUG>) - -target_link_libraries(test-qtapp - PRIVATE - Qt${QT_VERSION_MAJOR}::Core - Qt${QT_VERSION_MAJOR}::Quick - statusgo_shared) # <- Link status-go with this target (remove `_shared` for static linking) diff --git a/qt-example-app/README.md b/qt-example-app/README.md deleted file mode 100644 index ea51ea306f..0000000000 --- a/qt-example-app/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Example QT app - -``` -cd build -cmake .. -make -./test-qtapp -``` - -This example app just shows how to plug-in status-go in a qt-app. -Also check [status-cpp](https://github.com/richard-ramos/status-cpp) \ No newline at end of file diff --git a/qt-example-app/main.cpp b/qt-example-app/main.cpp deleted file mode 100644 index a39c9ffeec..0000000000 --- a/qt-example-app/main.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include -#include -#include - -extern "C" -{ -#include "libstatus.h" -} - -int main(int argc, char *argv[]) -{ - - QString hello = "Hello World!"; - const char* result = HashMessage(hello.toUtf8().data()); // <- HashMessage comes from libstatus.h - const auto response = QJsonDocument::fromJson(QString(result).toUtf8()).object(); // <- For now, status-go always returns json - if (!response["error"].isUndefined() || !response["error"].toString().isEmpty()){ // <- Always do error handling - std::cout << "Could not obtain hash: " << response["result"].toString().toStdString() << std::endl << std::flush; - } else { - std::cout << "Hash of HelloWorld: " << response["result"].toString().toStdString() << std::endl << std::flush; - } - - // For handling signals, see: - // https://github.com/richard-ramos/status-cpp/blob/master/src/core/status.cpp#L36 - // https://github.com/richard-ramos/status-cpp/blob/master/src/core/status.cpp#L124-L127 - - -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif - - QGuiApplication app(argc, argv); - - QQmlApplicationEngine engine; - const QUrl url(QStringLiteral("qrc:/main.qml")); - QObject::connect( - &engine, &QQmlApplicationEngine::objectCreated, - &app, [url](QObject *obj, const QUrl &objUrl) - { - if (!obj && url == objUrl) - QCoreApplication::exit(-1); - }, - Qt::QueuedConnection); - engine.load(url); - - return app.exec(); -} diff --git a/qt-example-app/main.qml b/qt-example-app/main.qml deleted file mode 100644 index f98fc7f151..0000000000 --- a/qt-example-app/main.qml +++ /dev/null @@ -1,9 +0,0 @@ -import QtQuick 2.14 -import QtQuick.Window 2.14 - -Window { - width: 640 - height: 480 - visible: true - title: qsTr("Hello World") -} diff --git a/qt-example-app/qml.qrc b/qt-example-app/qml.qrc deleted file mode 100644 index 5f6483ac33..0000000000 --- a/qt-example-app/qml.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - main.qml - - diff --git a/resources/default-networks.json b/resources/default-networks.json new file mode 100644 index 0000000000..830c9e107a --- /dev/null +++ b/resources/default-networks.json @@ -0,0 +1,76 @@ +[ + { + "id": "testnet_rpc", + "etherscan-link": "https://ropsten.etherscan.io/address/", + "name": "Ropsten with upstream RPC", + "config": { + "NetworkId": 3, + "DataDir": "/ethereum/testnet_rpc", + "UpstreamConfig": { + "Enabled": true, + "URL": "https://ropsten.infura.io/v3/%INFURA_KEY%" + } + } + }, + { + "id": "rinkeby_rpc", + "etherscan-link": "https://rinkeby.etherscan.io/address/", + "name": "Rinkeby with upstream RPC", + "config": { + "NetworkId": 4, + "DataDir": "/ethereum/rinkeby_rpc", + "UpstreamConfig": { + "Enabled": true, + "URL": "https://rinkeby.infura.io/v3/%INFURA_KEY%" + } + } + }, + { + "id": "goerli_rpc", + "etherscan-link": "https://goerli.etherscan.io/address/", + "name": "Goerli with upstream RPC", + "config": { + "NetworkId": 5, + "DataDir": "/ethereum/goerli_rpc", + "UpstreamConfig": { + "Enabled": true, + "URL": "https://goerli.blockscout.com/" + } + } + }, + { + "id": "mainnet_rpc", + "etherscan-link": "https://etherscan.io/address/", + "name": "Mainnet with upstream RPC", + "config": { + "NetworkId": 1, + "DataDir": "/ethereum/mainnet_rpc", + "UpstreamConfig": { + "Enabled": true, + "URL": "https://mainnet.infura.io/v3/%INFURA_KEY%" + } + } + }, + { + "id": "xdai_rpc", + "name": "xDai Chain", + "config": { + "NetworkId": 100, + "DataDir": "/ethereum/xdai_rpc", + "UpstreamConfig": { + "Enabled": true, + "URL": "https://dai.poa.network" + } + } + }, + { + "id": "poa_rpc", + "name": "POA Network", + "config": { + "NetworkId": 99, + "DataDir": "/ethereum/poa_rpc", + "UpstreamConfig": { "Enabled": true, "URL": "https://core.poa.network" } + } + } + ] + \ No newline at end of file diff --git a/resources/fleets.json b/resources/fleets.json new file mode 100644 index 0000000000..c31a3362a8 --- /dev/null +++ b/resources/fleets.json @@ -0,0 +1,143 @@ +{ + "fleets": { + "eth.prod": { + "boot": { + "boot-01.ac-cn-hongkong-c.eth.prod": "enode://6e6554fb3034b211398fcd0f0082cbb6bd13619e1a7e76ba66e1809aaa0c5f1ac53c9ae79cf2fd4a7bacb10d12010899b370c75fed19b991d9c0cdd02891abad@47.75.99.169:443", + "boot-01.do-ams3.eth.prod": "enode://436cc6f674928fdc9a9f7990f2944002b685d1c37f025c1be425185b5b1f0900feaf1ccc2a6130268f9901be4a7d252f37302c8335a2c1a62736e9232691cc3a@178.128.138.128:443", + "boot-01.gc-us-central1-a.eth.prod": "enode://32ff6d88760b0947a3dee54ceff4d8d7f0b4c023c6dad34568615fcae89e26cc2753f28f12485a4116c977be937a72665116596265aa0736b53d46b27446296a@34.70.75.208:443", + "boot-02.ac-cn-hongkong-c.eth.prod": "enode://23d0740b11919358625d79d4cac7d50a34d79e9c69e16831c5c70573757a1f5d7d884510bc595d7ee4da3c1508adf87bbc9e9260d804ef03f8c1e37f2fb2fc69@47.52.106.107:443", + "boot-02.do-ams3.eth.prod": "enode://5395aab7833f1ecb671b59bf0521cf20224fe8162fc3d2675de4ee4d5636a75ec32d13268fc184df8d1ddfa803943906882da62a4df42d4fccf6d17808156a87@178.128.140.188:443", + "boot-02.gc-us-central1-a.eth.prod": "enode://5405c509df683c962e7c9470b251bb679dd6978f82d5b469f1f6c64d11d50fbd5dd9f7801c6ad51f3b20a5f6c7ffe248cc9ab223f8bcbaeaf14bb1c0ef295fd0@35.223.215.156:443" + }, + "mail": { + "mail-01.ac-cn-hongkong-c.eth.prod": "enode://606ae04a71e5db868a722c77a21c8244ae38f1bd6e81687cc6cfe88a3063fa1c245692232f64f45bd5408fed5133eab8ed78049332b04f9c110eac7f71c1b429@47.75.247.214:443", + "mail-01.do-ams3.eth.prod": "enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@178.128.142.54:443", + "mail-01.gc-us-central1-a.eth.prod": "enode://ee2b53b0ace9692167a410514bca3024695dbf0e1a68e1dff9716da620efb195f04a4b9e873fb9b74ac84de801106c465b8e2b6c4f0d93b8749d1578bfcaf03e@104.197.238.144:443", + "mail-02.ac-cn-hongkong-c.eth.prod": "enode://2c8de3cbb27a3d30cbb5b3e003bc722b126f5aef82e2052aaef032ca94e0c7ad219e533ba88c70585ebd802de206693255335b100307645ab5170e88620d2a81@47.244.221.14:443", + "mail-02.do-ams3.eth.prod": "enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@178.128.142.26:443", + "mail-02.gc-us-central1-a.eth.prod": "enode://30211cbd81c25f07b03a0196d56e6ce4604bb13db773ff1c0ea2253547fafd6c06eae6ad3533e2ba39d59564cfbdbb5e2ce7c137a5ebb85e99dcfc7a75f99f55@23.236.58.92:443", + "mail-03.ac-cn-hongkong-c.eth.prod": "enode://e85f1d4209f2f99da801af18db8716e584a28ad0bdc47fbdcd8f26af74dbd97fc279144680553ec7cd9092afe683ddea1e0f9fc571ebcb4b1d857c03a088853d@47.244.129.82:443", + "mail-03.do-ams3.eth.prod": "enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@178.128.142.94:443", + "mail-03.gc-us-central1-a.eth.prod": "enode://44160e22e8b42bd32a06c1532165fa9e096eebedd7fa6d6e5f8bbef0440bc4a4591fe3651be68193a7ec029021cdb496cfe1d7f9f1dc69eb99226e6f39a7a5d4@35.225.221.245:443" + }, + "rendezvous": { + "boot-01.ac-cn-hongkong-c.eth.prod": "/ip4/47.75.99.169/tcp/30703/ethv4/16Uiu2HAmV8Hq9e3zm9TMVP4zrVHo3BjqW5D6bDVV6VQntQd687e4", + "boot-01.do-ams3.eth.prod": "/ip4/178.128.138.128/tcp/30703/ethv4/16Uiu2HAmRHPzF3rQg55PgYPcQkyvPVH9n2hWsYPhUJBZ6kVjJgdV", + "boot-01.gc-us-central1-a.eth.prod": "/ip4/34.70.75.208/tcp/30703/ethv4/16Uiu2HAm6ZsERLx2BwVD2UM9SVPnnMU6NBycG8XPtu8qKys5awsU", + "boot-02.ac-cn-hongkong-c.eth.prod": "/ip4/47.52.106.107/tcp/30703/ethv4/16Uiu2HAmEHiptiDDd9gqNY8oQqo8hHUWMHJzfwt5aLRdD6W2zcXR", + "boot-02.do-ams3.eth.prod": "/ip4/178.128.140.188/tcp/30703/ethv4/16Uiu2HAmLqTXuY4Sb6G28HNooaFUXUKzpzKXCcgyJxgaEE2i5vnf", + "boot-02.gc-us-central1-a.eth.prod": "/ip4/35.223.215.156/tcp/30703/ethv4/16Uiu2HAmQEUFE2YaJohavWtHxPTEFv3sEGJtDqvtGEv78DFoEWQF" + }, + "whisper": { + "node-01.ac-cn-hongkong-c.eth.prod": "enode://b957e51f41e4abab8382e1ea7229e88c6e18f34672694c6eae389eac22dab8655622bbd4a08192c321416b9becffaab11c8e2b7a5d0813b922aa128b82990dab@47.75.222.178:443", + "node-01.do-ams3.eth.prod": "enode://66ba15600cda86009689354c3a77bdf1a97f4f4fb3ab50ffe34dbc904fac561040496828397be18d9744c75881ffc6ac53729ddbd2cdbdadc5f45c400e2622f7@178.128.141.87:443", + "node-01.gc-us-central1-a.eth.prod": "enode://182ed5d658d1a1a4382c9e9f7c9e5d8d9fec9db4c71ae346b9e23e1a589116aeffb3342299bdd00e0ab98dbf804f7b2d8ae564ed18da9f45650b444aed79d509@34.68.132.118:443", + "node-02.ac-cn-hongkong-c.eth.prod": "enode://8bebe73ddf7cf09e77602c7d04c93a73f455b51f24ae0d572917a4792f1dec0bb4c562759b8830cc3615a658d38c1a4a38597a1d7ae3ba35111479fc42d65dec@47.75.85.212:443", + "node-02.do-ams3.eth.prod": "enode://4ea35352702027984a13274f241a56a47854a7fd4b3ba674a596cff917d3c825506431cf149f9f2312a293bb7c2b1cca55db742027090916d01529fe0729643b@134.209.136.79:443", + "node-02.gc-us-central1-a.eth.prod": "enode://fbeddac99d396b91d59f2c63a3cb5fc7e0f8a9f7ce6fe5f2eed5e787a0154161b7173a6a73124a4275ef338b8966dc70a611e9ae2192f0f2340395661fad81c0@34.67.230.193:443", + "node-03.ac-cn-hongkong-c.eth.prod": "enode://ac3948b2c0786ada7d17b80cf869cf59b1909ea3accd45944aae35bf864cc069126da8b82dfef4ddf23f1d6d6b44b1565c4cf81c8b98022253c6aea1a89d3ce2@47.75.88.12:443", + "node-03.do-ams3.eth.prod": "enode://ce559a37a9c344d7109bd4907802dd690008381d51f658c43056ec36ac043338bd92f1ac6043e645b64953b06f27202d679756a9c7cf62fdefa01b2e6ac5098e@134.209.136.123:443", + "node-03.gc-us-central1-a.eth.prod": "enode://c07aa0deea3b7056c5d45a85bca42f0d8d3b1404eeb9577610f386e0a4744a0e7b2845ae328efc4aa4b28075af838b59b5b3985bffddeec0090b3b7669abc1f3@35.226.92.155:443", + "node-04.ac-cn-hongkong-c.eth.prod": "enode://385579fc5b14e04d5b04af7eee835d426d3d40ccf11f99dbd95340405f37cf3bbbf830b3eb8f70924be0c2909790120682c9c3e791646e2d5413e7801545d353@47.244.221.249:443", + "node-04.do-ams3.eth.prod": "enode://4e0a8db9b73403c9339a2077e911851750fc955db1fc1e09f81a4a56725946884dd5e4d11258eac961f9078a393c45bcab78dd0e3bc74e37ce773b3471d2e29c@134.209.136.101:443", + "node-04.gc-us-central1-a.eth.prod": "enode://0624b4a90063923c5cc27d12624b6a49a86dfb3623fcb106801217fdbab95f7617b83fa2468b9ae3de593ff6c1cf556ccf9bc705bfae9cb4625999765127b423@35.222.158.246:443", + "node-05.ac-cn-hongkong-c.eth.prod": "enode://b77bffc29e2592f30180311dd81204ab845e5f78953b5ba0587c6631be9c0862963dea5eb64c90617cf0efd75308e22a42e30bc4eb3cd1bbddbd1da38ff6483e@47.75.10.177:443", + "node-05.do-ams3.eth.prod": "enode://a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c@178.128.141.249:443", + "node-05.gc-us-central1-a.eth.prod": "enode://a5fe9c82ad1ffb16ae60cb5d4ffe746b9de4c5fbf20911992b7dd651b1c08ba17dd2c0b27ee6b03162c52d92f219961cc3eb14286aca8a90b75cf425826c3bd8@104.154.230.58:443", + "node-06.ac-cn-hongkong-c.eth.prod": "enode://cf5f7a7e64e3b306d1bc16073fba45be3344cb6695b0b616ccc2da66ea35b9f35b3b231c6cf335fdfaba523519659a440752fc2e061d1e5bc4ef33864aac2f19@47.75.221.196:443", + "node-06.do-ams3.eth.prod": "enode://887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875@178.128.141.0:443", + "node-06.gc-us-central1-a.eth.prod": "enode://282e009967f9f132a5c2dd366a76319f0d22d60d0c51f7e99795a1e40f213c2705a2c10e4cc6f3890319f59da1a535b8835ed9b9c4b57c3aad342bf312fd7379@35.223.240.17:443", + "node-07.ac-cn-hongkong-c.eth.prod": "enode://13d63a1f85ccdcbd2fb6861b9bd9d03f94bdba973608951f7c36e5df5114c91de2b8194d71288f24bfd17908c48468e89dd8f0fb8ccc2b2dedae84acdf65f62a@47.244.210.80:443", + "node-07.do-ams3.eth.prod": "enode://2b01955d7e11e29dce07343b456e4e96c081760022d1652b1c4b641eaf320e3747871870fa682e9e9cfb85b819ce94ed2fee1ac458904d54fd0b97d33ba2c4a4@134.209.136.112:443", + "node-07.gc-us-central1-a.eth.prod": "enode://b706a60572634760f18a27dd407b2b3582f7e065110dae10e3998498f1ae3f29ba04db198460d83ed6d2bfb254bb06b29aab3c91415d75d3b869cd0037f3853c@35.239.5.162:443", + "node-08.ac-cn-hongkong-c.eth.prod": "enode://32915c8841faaef21a6b75ab6ed7c2b6f0790eb177ad0f4ea6d731bacc19b938624d220d937ebd95e0f6596b7232bbb672905ee12601747a12ee71a15bfdf31c@47.75.59.11:443", + "node-08.do-ams3.eth.prod": "enode://0d9d65fcd5592df33ed4507ce862b9c748b6dbd1ea3a1deb94e3750052760b4850aa527265bbaf357021d64d5cc53c02b410458e732fafc5b53f257944247760@178.128.141.42:443", + "node-08.gc-us-central1-a.eth.prod": "enode://e87f1d8093d304c3a9d6f1165b85d6b374f1c0cc907d39c0879eb67f0a39d779be7a85cbd52920b6f53a94da43099c58837034afa6a7be4b099bfcd79ad13999@35.238.106.101:443" + } + }, + "eth.staging": { + "boot": { + "boot-01.ac-cn-hongkong-c.eth.staging": "enode://630b0342ca4e9552f50714b6c8e28d6955bc0fd14e7950f93bc3b2b8cc8c1f3b6d103df66f51a13d773b5db0f130661fb5c7b8fa21c48890c64c79b41a56a490@47.91.229.44:443", + "boot-01.do-ams3.eth.staging": "enode://f79fb3919f72ca560ad0434dcc387abfe41e0666201ebdada8ede0462454a13deb05cda15f287d2c4bd85da81f0eb25d0a486bbbc8df427b971ac51533bd00fe@174.138.107.239:443", + "boot-01.gc-us-central1-a.eth.staging": "enode://10a78c17929a7019ef4aa2249d7302f76ae8a06f40b2dc88b7b31ebff4a623fbb44b4a627acba296c1ced3775d91fbe18463c15097a6a36fdb2c804ff3fc5b35@35.238.97.234:443" + }, + "mail": { + "mail-01.ac-cn-hongkong-c.eth.staging": "enode://b74859176c9751d314aeeffc26ec9f866a412752e7ddec91b19018a18e7cca8d637cfe2cedcb972f8eb64d816fbd5b4e89c7e8c7fd7df8a1329fa43db80b0bfe@47.52.90.156:443", + "mail-01.do-ams3.eth.staging": "enode://69f72baa7f1722d111a8c9c68c39a31430e9d567695f6108f31ccb6cd8f0adff4991e7fdca8fa770e75bc8a511a87d24690cbc80e008175f40c157d6f6788d48@206.189.240.16:443", + "mail-01.gc-us-central1-a.eth.staging": "enode://e4fc10c1f65c8aed83ac26bc1bfb21a45cc1a8550a58077c8d2de2a0e0cd18e40fd40f7e6f7d02dc6cd06982b014ce88d6e468725ffe2c138e958788d0002a7f@35.239.193.41:443" + }, + "rendezvous": { + "boot-01.ac-cn-hongkong-c.eth.staging": "/ip4/47.91.229.44/tcp/30703/ethv4/16Uiu2HAmRnt2Eyoknh3auxh4fJwkRgqkH1gqrWGes8Pk1k3MV4xu", + "boot-01.do-ams3.eth.staging": "/ip4/174.138.107.239/tcp/30703/ethv4/16Uiu2HAm8UZXUHEPZrpJbcQ3yVFH6UtKrwsG6jH4ai72PsbLfVFb", + "boot-01.gc-us-central1-a.eth.staging": "/ip4/35.238.97.234/tcp/30703/ethv4/16Uiu2HAm6G9sDMkrB4Xa5EH3Zx2dysCxFgBTSRzghic3Z9tRFRNE" + }, + "whisper": { + "node-01.ac-cn-hongkong-c.eth.staging": "enode://088cf5a93c576fae52f6f075178467b8ff98bacf72f59e88efb16dfba5b30f80a4db78f8e3cb3d87f2f6521746ef4a8768465ef2896c6af24fd77a425e95b6dd@47.52.226.137:443", + "node-01.do-ams3.eth.staging": "enode://914c0b30f27bab30c1dfd31dad7652a46fda9370542aee1b062498b1345ee0913614b8b9e3e84622e84a7203c5858ae1d9819f63aece13ee668e4f6668063989@167.99.19.148:443", + "node-01.gc-us-central1-a.eth.staging": "enode://d3878441652f010326889f28360e69f2d09d06540f934fada0e17b374ce5319de64279aba3c44a5bf807d9967c6d705b3b4c6b03fa70763240e2ee6af01a539e@35.192.0.86:443" + } + }, + "eth.test": { + "boot": { + "boot-01.ac-cn-hongkong-c.eth.test": "enode://daae2e72820e86e942fa2a8aa7d6e9954d4043a753483d8bd338e16be82cf962392d5c0e1ae57c3d793c3d3dddd8fd58339262e4234dc966f953cd73b535f5fa@47.52.188.149:443", + "boot-01.do-ams3.eth.test": "enode://9e0988575eb7717c25dea72fd11c7b37767dc09c1a7686f7c2ec577d308d24b377ceb675de4317474a1a870e47882732967f4fa785b02ba95d669b31d464dec0@206.189.243.164:443", + "boot-01.gc-us-central1-a.eth.test": "enode://c1e5018887c863d64e431b69bf617561087825430e4401733f5ba77c70db14236df381fefb0ebe1ac42294b9e261bbe233dbdb83e32c586c66ae26c8de70cb4c@35.188.168.137:443" + }, + "mail": { + "mail-01.ac-cn-hongkong-c.eth.test": "enode://619dbb5dda12e85bf0eb5db40fb3de625609043242737c0e975f7dfd659d85dc6d9a84f9461a728c5ab68c072fed38ca6a53917ca24b8e93cc27bdef3a1e79ac@47.52.188.196:443", + "mail-01.do-ams3.eth.test": "enode://e4865fe6c2a9c1a563a6447990d8e9ce672644ae3e08277ce38ec1f1b690eef6320c07a5d60c3b629f5d4494f93d6b86a745a0bf64ab295bbf6579017adc6ed8@206.189.243.161:443", + "mail-01.gc-us-central1-a.eth.test": "enode://707e57453acd3e488c44b9d0e17975371e2f8fb67525eae5baca9b9c8e06c86cde7c794a6c2e36203bf9f56cae8b0e50f3b33c4c2b694a7baeea1754464ce4e3@35.192.229.172:443" + }, + "rendezvous": { + "boot-01.ac-cn-hongkong-c.eth.test": "/ip4/47.52.188.149/tcp/30703/ethv4/16Uiu2HAm9Vatqr4GfVCqnyeaPtCF3q8fz8kDDUgqXVfFG7ZfSA7w", + "boot-01.do-ams3.eth.test": "/ip4/206.189.243.164/tcp/30703/ethv4/16Uiu2HAmBCh5bgYr6V3fDuLqUzvtSAsFTQJCQ3TVHT8ta8bTu2Jm", + "boot-01.gc-us-central1-a.eth.test": "/ip4/35.188.168.137/tcp/30703/ethv4/16Uiu2HAm3MUqtGjmetyZ9L4SN2R8oHDWvACUcec25LjtDD5euiRH" + }, + "whisper": { + "node-01.ac-cn-hongkong-c.eth.test": "enode://ad38f94030a846cc7005b7a1f3b6b01bf4ef59d34e8d3d6f4d12df23d14ba8656702a435d34cf4df3b412c0c1923df5adcce8461321a0d8ffb9435b26e572c2a@47.52.255.194:443", + "node-01.do-ams3.eth.test": "enode://1d193635e015918fb85bbaf774863d12f65d70c6977506187ef04420d74ec06c9e8f0dcb57ea042f85df87433dab17a1260ed8dde1bdf9d6d5d2de4b7bf8e993@206.189.243.163:443", + "node-01.gc-us-central1-a.eth.test": "enode://f593a27731bc0f8eb088e2d39222c2d59dfb9bf0b3950d7a828d51e8ab9e08fffbd9916a82fd993c1a080c57c2bd70ed6c36f489a969de697aff93088dbee1a9@35.194.31.108:443" + } + }, + "go-waku.test": { + "libp2p": { + "node-01.ac-cn-hongkong-c.go-waku.test": "/ip4/8.218.2.110/tcp/30303/p2p/16Uiu2HAmBDbMWFiG9ki8sDw6fYtraSxo4oHU9HbuN43S2HVyq1FD", + "node-01.do-ams3.go-waku.test": "/ip4/134.209.134.63/tcp/30303/p2p/16Uiu2HAm9vnvCQgCDrynDK1h7GJoEZVGvnuzq84RyDQ3DEdXmcX7", + "node-01.gc-us-central1-a.go-waku.test": "/ip4/35.223.183.91/tcp/30303/p2p/16Uiu2HAmPz63Xc6AuVkDeujz7YeZta18rcdau3Y1BzaxKAfDrBqz" + }, + "websocket": { + "node-01.ac-cn-hongkong-c.go-waku.test": "/dns4/node-01.ac-cn-hongkong-c.go-waku.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmBDbMWFiG9ki8sDw6fYtraSxo4oHU9HbuN43S2HVyq1FD", + "node-01.do-ams3.go-waku.test": "/dns4/node-01.do-ams3.go-waku.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAm9vnvCQgCDrynDK1h7GJoEZVGvnuzq84RyDQ3DEdXmcX7", + "node-01.gc-us-central1-a.go-waku.test": "/dns4/node-01.gc-us-central1-a.go-waku.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPz63Xc6AuVkDeujz7YeZta18rcdau3Y1BzaxKAfDrBqz" + } + }, + "wakuv2.prod": { + "waku": { + "node-01.ac-cn-hongkong-c.wakuv2.prod": "/ip4/8.210.222.231/tcp/30303/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD", + "node-01.do-ams3.wakuv2.prod": "/ip4/188.166.135.145/tcp/30303/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e", + "node-01.gc-us-central1-a.wakuv2.prod": "/ip4/34.121.100.108/tcp/30303/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA" + }, + "waku-websocket": { + "node-01.ac-cn-hongkong-c.wakuv2.prod": "/dns4/node-01.ac-cn-hongkong-c.wakuv2.prod.statusim.net/tcp/443/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD", + "node-01.do-ams3.wakuv2.prod": "/dns4/node-01.do-ams3.wakuv2.prod.statusim.net/tcp/443/wss/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e", + "node-01.gc-us-central1-a.wakuv2.prod": "/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/443/wss/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA" + } + }, + "wakuv2.test": { + "waku": { + "node-01.ac-cn-hongkong-c.wakuv2.test": "/ip4/47.242.210.73/tcp/30303/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm", + "node-01.do-ams3.wakuv2.test": "/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ", + "node-01.gc-us-central1-a.wakuv2.test": "/ip4/104.154.239.128/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS" + }, + "waku-websocket": { + "node-01.ac-cn-hongkong-c.wakuv2.test": "/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm", + "node-01.do-ams3.wakuv2.test": "/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ", + "node-01.gc-us-central1-a.wakuv2.test": "/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS" + } + } + }, + "meta": { + "hostname": "node-01.do-ams3.sites.misc", + "timestamp": "2021-10-19T00:00:15.465044" + } +} diff --git a/resources/infura_key b/resources/infura_key new file mode 100644 index 0000000000..7b994ee0ea --- /dev/null +++ b/resources/infura_key @@ -0,0 +1 @@ +220a1abb4b6943a093c35d0ce4fb0732 \ No newline at end of file diff --git a/resources/node-config.json b/resources/node-config.json new file mode 100644 index 0000000000..55a7d733e2 --- /dev/null +++ b/resources/node-config.json @@ -0,0 +1,65 @@ +{ + "BrowsersConfig": { + "Enabled": true + }, + "ClusterConfig": { + "Enabled": true + }, + "DataDir": "./ethereum/mainnet", + "EnableNTPSync": true, + "KeyStoreDir": "./keystore", + "LogEnabled": true, + "LogFile": "./geth.log", + "LogLevel": "INFO", + "MailserversConfig": { + "Enabled": true + }, + "Name": "StatusDesktop", + "NetworkId": 1, + "NoDiscovery": false, + "PermissionsConfig": { + "Enabled": true + }, + "Rendezvous": true, + "RegisterTopics": ["whispermail"], + "RequireTopics": { + "whisper": { + "Max": 2, + "Min": 2 + } + }, + "ShhextConfig": { + "BackupDisabledDataDir": "./", + "DataSyncEnabled": true, + "InstallationID": "%INSTALLATIONID%", + "MailServerConfirmations": true, + "MaxMessageDeliveryAttempts": 6, + "PFSEnabled": true, + "VerifyENSContractAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "VerifyENSURL": "https://mainnet.infura.io/v3/%INFURA_KEY%", + "VerifyTransactionChainID": 1, + "VerifyTransactionURL": "https://mainnet.infura.io/v3/%INFURA_KEY%" + }, + "StatusAccountsConfig": { + "Enabled": true + }, + "UpstreamConfig": { + "Enabled": true, + "URL": "https://mainnet.infura.io/v3/%INFURA_KEY%" + }, + "WakuConfig": { + "BloomFilterMode": null, + "Enabled": true, + "LightClient": true, + "MinimumPoW": 0.001 + }, + "WakuV2Config": { + "Enabled": false, + "Host": "0.0.0.0", + "Port": 0, + "LightClient": false + }, + "WalletConfig": { + "Enabled": true + } +} diff --git a/src-cpp/app/CMakeLists.txt b/src-cpp/app/CMakeLists.txt new file mode 100644 index 0000000000..7288aa09ee --- /dev/null +++ b/src-cpp/app/CMakeLists.txt @@ -0,0 +1,36 @@ +add_library(app + include/signals.h + boot/app_controller.cpp + core/signals/signals.cpp + global/singleton.cpp + modules/startup/controller.cpp + modules/startup/module.cpp + modules/startup/view.cpp + modules/startup/onboarding/controller.cpp + modules/startup/onboarding/item.cpp + modules/startup/onboarding/model.cpp + modules/startup/onboarding/module.cpp + modules/startup/onboarding/view.cpp + modules/startup/login/controller.cpp + modules/startup/login/item.cpp + modules/startup/login/model.cpp + modules/startup/login/module.cpp + modules/startup/login/view.cpp + modules/startup/login/selected_account.cpp + +) + +target_include_directories(app + PUBLIC + ./include/ + ./modules/startup +) + +target_link_libraries(app + PRIVATE + app_service + statusgo_shared + Qt${QT_VERSION_MAJOR}::Concurrent + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Quick +) diff --git a/src-cpp/app/boot/app_controller.cpp b/src-cpp/app/boot/app_controller.cpp new file mode 100644 index 0000000000..9f29bfd2c2 --- /dev/null +++ b/src-cpp/app/boot/app_controller.cpp @@ -0,0 +1,237 @@ +#include "app_controller.h" +#include "accounts/service.h" +#include "app_service.h" +#include "modules/startup/module.h" +#include + +AppController::AppController() +{ + // result.statusFoundation = statusFoundation + + // # Global + // result.localAppSettingsVariant = newQVariant(singletonInstance.localAppSettings) + // result.localAccountSettingsVariant = newQVariant(singletonInstance.localAccountSettings) + // result.localAccountSensitiveSettingsVariant = newQVariant(singletonInstance.localAccountSensitiveSettings) + // result.userProfileVariant = newQVariant(singletonInstance.userProfile) + // result.globalUtilsVariant = newQVariant(singletonInstance.utils) + + // # Services + // result.settingsService = settings_service.newService() + // result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration, + // result.settingsService) + // result.osNotificationService = os_notification_service.newService(statusFoundation.status.events) + // result.keychainService = keychain_service.newService(statusFoundation.status.events) + // result.ethService = eth_service.newService() + m_accountsService = new Accounts::Service(); + // result.networkService = network_service.newService() + // result.contactsService = contacts_service.newService(statusFoundation.status.events, statusFoundation.threadpool) + // result.chatService = chat_service.newService(statusFoundation.status.events, result.contactsService) + // result.communityService = community_service.newService(statusFoundation.status.events) + // result.messageService = message_service.newService(statusFoundation.status.events, statusFoundation.threadpool) + // result.activityCenterService = activity_center_service.newService(statusFoundation.status.events, + // statusFoundation.threadpool, result.chatService) + // result.tokenService = token_service.newService(statusFoundation.status.events, statusFoundation.threadpool, + // result.settingsService) + // result.collectibleService = collectible_service.newService(result.settingsService) + // result.walletAccountService = wallet_account_service.newService(statusFoundation.status.events, result.settingsService, + // result.accountsService, result.tokenService) + // result.transactionService = transaction_service.newService(statusFoundation.status.events, statusFoundation.threadpool, + // result.walletAccountService) + // result.bookmarkService = bookmark_service.newService() + // result.profileService = profile_service.newService() + // result.stickersService = stickers_service.newService( + // statusFoundation.status.events, + // statusFoundation.threadpool, + // result.ethService, + // result.settingsService, + // result.walletAccountService, + // result.transactionService, + // result.networkService, + // result.chatService + // ) + // result.aboutService = about_service.newService(statusFoundation.status.events, statusFoundation.threadpool, + // result.settingsService) + // result.dappPermissionsService = dapp_permissions_service.newService() + // result.languageService = language_service.newService() + // # result.mnemonicService = mnemonic_service.newService() + // result.privacyService = privacy_service.newService(statusFoundation.status.events, result.settingsService, + // result.accountsService) + // result.providerService = provider_service.newService(result.dappPermissionsService, result.settingsService) + // result.savedAddressService = saved_address_service.newService(statusFoundation.status.events) + // result.devicesService = devices_service.newService(statusFoundation.status.events, result.settingsService) + // result.mailserversService = mailservers_service.newService(statusFoundation.status.events, statusFoundation.marathon, + // result.settingsService, result.nodeConfigurationService, statusFoundation.fleetConfiguration) + + // # Modules + m_startupModule = new Modules::Startup::Module(this, /*keychainService,*/ m_accountsService); + + // result.mainModule = main_module.newModule[AppController]( + // result, + // statusFoundation.status.events, + // result.keychainService, + // result.accountsService, + // result.chatService, + // result.communityService, + // result.messageService, + // result.tokenService, + // result.transactionService, + // result.collectibleService, + // result.walletAccountService, + // result.bookmarkService, + // result.profileService, + // result.settingsService, + // result.contactsService, + // result.aboutService, + // result.dappPermissionsService, + // result.languageService, + // # result.mnemonicService, + // result.privacyService, + // result.providerService, + // result.stickersService, + // result.activityCenterService, + // result.savedAddressService, + // result.nodeConfigurationService, + // result.devicesService, + // result.mailserversService + // ) + + // # Do connections + connect(); +} + +AppController::~AppController() +{ + delete m_startupModule; + + delete m_accountsService; +} + +void AppController::connect() +{ + // self.statusFoundation.status.events.once("nodeStopped") do(a: Args): + // TODO: remove this once accounts are not tracked in the AccountsModel + // self.statusFoundation.status.reset() +} + +void AppController::startupDidLoad() +{ + // singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant) + // singletonInstance.engine.setRootContextProperty("localAccountSettings", self.localAccountSettingsVariant) + // singletonInstance.engine.load(newQUrl("qrc:///main.qml")) + + // We need to init a language service once qml is loaded + // self.languageService.init() +} + +void AppController::mainDidLoad() +{ + //self.statusFoundation.onLoggedIn() + m_startupModule->moveToAppState(); + + //self.mainModule.checkForStoringPassword() +} + +void AppController::start() +{ + // self.ethService.init() + m_accountsService->init(); + + m_startupModule->load(); +} + +void AppController::load() +{ + qWarning() << "TODO: init services and load main module"; + // self.settingsService.init() + // self.nodeConfigurationService.init() + // self.contactsService.init() + // self.chatService.init() + // self.messageService.init() + // self.communityService.init() + // self.bookmarkService.init() + // self.tokenService.init() + // self.dappPermissionsService.init() + // self.providerService.init() + // self.walletAccountService.init() + // self.transactionService.init() + // self.stickersService.init() + // self.networkService.init() + // self.activityCenterService.init() + // self.savedAddressService.init() + // self.aboutService.init() + // self.devicesService.init() + // self.mailserversService.init() + + // let pubKey = self.settingsService.getPublicKey() + // singletonInstance.localAccountSensitiveSettings.setFileName(pubKey) + // singletonInstance.engine.setRootContextProperty("localAccountSensitiveSettings", self.localAccountSensitiveSettingsVariant) + // singletonInstance.engine.setRootContextProperty("globalUtils", self.globalUtilsVariant) + + // # other global instances + // self.buildAndRegisterLocalAccountSensitiveSettings() + // self.buildAndRegisterUserProfile() + + // # load main module + // self.mainModule.load( + // self.statusFoundation.status.events, + // self.settingsService, + // self.contactsService, + // self.chatService, + // self.communityService, + // self.messageService + // ) +} + +void AppController::userLoggedIn() +{ + //self.statusFoundation.status.startMessenger() + AppController::load(); + + // Once user is logged in and main module is loaded we need to check if it gets here importing mnemonic or not + // and delete mnemonic in the first case. + auto importedAccount = m_accountsService->getImportedAccount(); + if(importedAccount.isValid()) + { + // self.privacyService.removeMnemonic(); + } +} + +void AppController::buildAndRegisterLocalAccountSensitiveSettings() +{ + + // var pubKey = self.settingsService.getPublicKey() + // singletonInstance.localAccountSensitiveSettings.setFileName(pubKey) + // singletonInstance.engine.setRootContextProperty("localAccountSensitiveSettings", self.localAccountSensitiveSettingsVariant) +} + +void AppController::buildAndRegisterUserProfile() +{ + // let pubKey = self.settingsService.getPublicKey() + // let preferredName = self.settingsService.getPreferredName() + // let ensUsernames = self.settingsService.getEnsUsernames() + // let firstEnsName = if (ensUsernames.len > 0): ensUsernames[0] else: "" + // let sendUserStatus = self.settingsService.getSendStatusUpdates() + // // This is still not in use. Read a comment in UserProfile. + // // let currentUserStatus = self.settingsService.getCurrentUserStatus() + + // let loggedInAccount = self.accountsService.getLoggedInAccount() + // var thumbnail, large: string + // for img in loggedInAccount.images: + // if(img.imgType == "large"): + // large = img.uri + // elif(img.imgType == "thumbnail"): + // thumbnail = img.uri + + // let meAsContact = self.contactsService.getContactById(pubKey) + + // singletonInstance.userProfile.setFixedData(loggedInAccount.name, loggedInAccount.keyUid, loggedInAccount.identicon, + // pubKey) + // singletonInstance.userProfile.setPreferredName(preferredName) + // singletonInstance.userProfile.setEnsName(meAsContact.name) + // singletonInstance.userProfile.setFirstEnsName(firstEnsName) + // singletonInstance.userProfile.setThumbnailImage(thumbnail) + // singletonInstance.userProfile.setLargeImage(large) + // singletonInstance.userProfile.setUserStatus(sendUserStatus) + + // singletonInstance.engine.setRootContextProperty("userProfile", self.userProfileVariant) +} diff --git a/src-cpp/app/core/signals/signals.cpp b/src-cpp/app/core/signals/signals.cpp new file mode 100644 index 0000000000..5231f64738 --- /dev/null +++ b/src-cpp/app/core/signals/signals.cpp @@ -0,0 +1,91 @@ +#include "signals.h" +#include "libstatus.h" +#include +#include +#include +#include +#include + +namespace Signals +{ + +Manager* Manager::theInstance; + +Manager* Manager::instance() +{ + if(theInstance == 0) theInstance = new Manager(); + return theInstance; +} + +std::map Manager::signalMap; + +Manager::Manager(QObject* parent) + : QObject(parent) +{ + SetSignalEventCallback((void*)&Manager::signalCallback); + + signalMap = {{"node.ready", SignalType::NodeReady}, + {"node.started", SignalType::NodeStarted}, + {"node.stopped", SignalType::NodeStopped}, + {"node.login", SignalType::NodeLogin}, + {"node.crashed", SignalType::NodeCrashed}}; +} + +void Manager::processSignal(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 Manager::decode(const QJsonObject& signalEvent) +{ + SignalType signalType(Unknown); + if(!signalMap.count(signalEvent["type"].toString())) + { + qWarning() << "Unknown signal received: " << signalEvent["type"].toString(); + return; + } + + signalType = signalMap[signalEvent["type"].toString()]; + + switch(signalType) + { + // TODO: create extractor functions like in nim + case NodeLogin: emit instance()->nodeLogin(NodeSignal{signalType, signalEvent["event"]["error"].toString()}); break; + case NodeReady: emit instance()->nodeReady(NodeSignal{signalType, signalEvent["event"]["error"].toString()}); break; + case NodeStarted: + emit instance()->nodeStarted(NodeSignal{signalType, signalEvent["event"]["error"].toString()}); + break; + case NodeStopped: + emit instance()->nodeStopped(NodeSignal{signalType, signalEvent["event"]["error"].toString()}); + break; + case NodeCrashed: { + auto signal = NodeSignal{signalType, signalEvent["event"]["error"].toString()}; + qWarning() << "node.crashed, error: " << signal.error; + emit instance()->nodeCrashed(signal); + break; + } + default: qWarning() << "Signal decoding not implemented: " << signalEvent; break; + } +} + +void Manager::signalCallback(const char* data) +{ + QtConcurrent::run(instance(), &Manager::processSignal, QString(data)); +} + +} // namespace Signals \ No newline at end of file diff --git a/src-cpp/app/global/singleton.cpp b/src-cpp/app/global/singleton.cpp new file mode 100644 index 0000000000..04dbf3b8d6 --- /dev/null +++ b/src-cpp/app/global/singleton.cpp @@ -0,0 +1,23 @@ +#include "singleton.h" +#include + +namespace Global +{ +Singleton* Singleton::theInstance; + +Singleton* Singleton::instance() +{ + if(theInstance == 0) theInstance = new Singleton(); + return theInstance; +} + +Singleton::Singleton() +{ + m_engine = new QQmlApplicationEngine(); +} + +QQmlApplicationEngine* Singleton::engine() +{ + return m_engine; +} +} // namespace Global diff --git a/src-cpp/app/include/app_controller.h b/src-cpp/app/include/app_controller.h new file mode 100644 index 0000000000..6759c936e6 --- /dev/null +++ b/src-cpp/app/include/app_controller.h @@ -0,0 +1,39 @@ +#pragma once + +#include "accounts/service.h" +#include "module_access_interface.h" +#include "app_controller_delegate.h" +#include "app_service.h" + +class AppController : public AppControllerDelegate +{ + //statusFoundation: StatusFoundation + + // Global + //localAppSettingsVariant: QVariant + //localAccountSettingsVariant: QVariant + //localAccountSensitiveSettingsVariant: QVariant + //userProfileVariant: QVariant + //globalUtilsVariant: QVariant + + // Services + Accounts::Service* m_accountsService; + + // Modules + Modules::Startup::ModuleAccessInterface* m_startupModule; + //mainModule: main_module.AccessInterface + +public: + AppController(); + ~AppController(); + void start(); + +private: + void connect(); + void startupDidLoad() override; + void mainDidLoad(); + void load(); + void userLoggedIn() override; + void buildAndRegisterLocalAccountSensitiveSettings(); + void buildAndRegisterUserProfile(); +}; diff --git a/src-cpp/app/include/app_controller_delegate.h b/src-cpp/app/include/app_controller_delegate.h new file mode 100644 index 0000000000..03d54dc700 --- /dev/null +++ b/src-cpp/app/include/app_controller_delegate.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class AppControllerDelegate +{ +public: + virtual void startupDidLoad() + { + throw std::domain_error("Not implemented"); + } + + virtual void userLoggedIn() + { + throw std::domain_error("Not implemented"); + } +}; diff --git a/src-cpp/app/include/signals.h b/src-cpp/app/include/signals.h new file mode 100644 index 0000000000..9635fb84f7 --- /dev/null +++ b/src-cpp/app/include/signals.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include + +namespace Signals +{ +Q_NAMESPACE + +enum SignalType +{ + Unknown, + NodeLogin, + NodeReady, + NodeStarted, + NodeStopped, + NodeCrashed +}; + +Q_ENUM_NS(SignalType) + +struct Signal +{ + SignalType signalType; +}; + +struct NodeSignal : Signal +{ + QString error; +}; + +class Manager : public QObject +{ + Q_OBJECT + +public: + static Manager* instance(); + +signals: + void signal(SignalType signal); + + void nodeReady(NodeSignal signal); + void nodeStarted(NodeSignal signal); + void nodeStopped(NodeSignal signal); + void nodeLogin(NodeSignal signal); + void nodeCrashed(NodeSignal signal); + +private: + static Manager* theInstance; + explicit Manager(QObject* parent = nullptr); + static std::map signalMap; + static void signalCallback(const char* data); + void processSignal(QString ev); + void decode(const QJsonObject& signalEvent); +}; + +} // namespace Signals + +Q_DECLARE_METATYPE(Signals::Signal) +Q_DECLARE_METATYPE(Signals::NodeSignal) \ No newline at end of file diff --git a/src-cpp/app/include/singleton.h b/src-cpp/app/include/singleton.h new file mode 100644 index 0000000000..b7c4d633ca --- /dev/null +++ b/src-cpp/app/include/singleton.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace Global +{ + +class Singleton +{ +public: + QQmlApplicationEngine* engine(); + static Singleton* instance(); + +private: + static Singleton* theInstance; + explicit Singleton(); + QQmlApplicationEngine* m_engine; +}; + +} // namespace Global \ No newline at end of file diff --git a/src-cpp/app/modules/startup/controller.cpp b/src-cpp/app/modules/startup/controller.cpp new file mode 100644 index 0000000000..2787f71a48 --- /dev/null +++ b/src-cpp/app/modules/startup/controller.cpp @@ -0,0 +1,55 @@ +#include "controller.h" +#include "accounts/service_interface.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "signals.h" +#include + +namespace Modules +{ +namespace Startup +{ +Controller::Controller(ModuleControllerDelegateInterface* d, + Accounts::ServiceInterface* accountsService, + QObject* parent) + : QObject(parent) + , m_accountsService(accountsService) + , m_delegate(d) +{ } + +void Controller::init() +{ + QObject::connect(Signals::Manager::instance(), &Signals::Manager::nodeLogin, this, &Controller::onLogin); + QObject::connect(Signals::Manager::instance(), &Signals::Manager::nodeStopped, this, &Controller::onNodeStopped); + QObject::connect(Signals::Manager::instance(), &Signals::Manager::nodeReady, this, &Controller::onNodeReady); +} + +void Controller::onLogin(Signals::NodeSignal signal) +{ + if(signal.error.isEmpty()) + { + m_delegate->userLoggedIn(); + } + else + { + qWarning() << "error: methodName=init, errDescription=login error " << signal.error; + } +} + +void Controller::onNodeStopped(Signals::NodeSignal signal) +{ + // self.events.emit("nodeStopped", Args()) + m_accountsService->clear(); + m_delegate->emitLogOut(); +} + +void Controller::onNodeReady(Signals::NodeSignal signal) +{ + // self.events.emit("nodeReady", Args()) +} + +bool Controller::shouldStartWithOnboardingScreen() +{ + return m_accountsService->openedAccounts().size() == 0; +} +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/controller.h b/src-cpp/app/modules/startup/controller.h new file mode 100644 index 0000000000..e90c1432fb --- /dev/null +++ b/src-cpp/app/modules/startup/controller.h @@ -0,0 +1,32 @@ +#pragma once + +#include "accounts/service_interface.h" +#include "controller_interface.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "signals.h" +#include + +namespace Modules +{ +namespace Startup +{ + +class Controller : public QObject, ControllerInterface +{ +public: + Controller(ModuleControllerDelegateInterface* d, + Accounts::ServiceInterface* accountsService, + QObject* parent = nullptr); + void init() override; + bool shouldStartWithOnboardingScreen() override; + void onLogin(Signals::NodeSignal signal); + void onNodeStopped(Signals::NodeSignal signal); + void onNodeReady(Signals::NodeSignal signal); + +private: + Accounts::ServiceInterface* m_accountsService; + ModuleControllerDelegateInterface* m_delegate; +}; + +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/controller_interface.h b/src-cpp/app/modules/startup/controller_interface.h new file mode 100644 index 0000000000..ee6b80b7ed --- /dev/null +++ b/src-cpp/app/modules/startup/controller_interface.h @@ -0,0 +1,23 @@ +#pragma once + +namespace Modules +{ +namespace Startup +{ + +// Abstract class for any input/interaction with this module. + +class ControllerInterface +{ +public: + virtual void init() + { + throw std::domain_error("Not implemented"); + } + virtual bool shouldStartWithOnboardingScreen() + { + throw std::domain_error("Not implemented"); + } +}; +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/interfaces/module_controller_delegate_interface.h b/src-cpp/app/modules/startup/interfaces/module_controller_delegate_interface.h new file mode 100644 index 0000000000..59c9a8ab07 --- /dev/null +++ b/src-cpp/app/modules/startup/interfaces/module_controller_delegate_interface.h @@ -0,0 +1,26 @@ +#pragma once + +#include "accounts/service_interface.h" +#include "app_controller_delegate.h" +#include "controller.h" +#include + +namespace Modules +{ +namespace Startup +{ +class ModuleControllerDelegateInterface +{ +public: + virtual void userLoggedIn() + { + throw std::domain_error("Not implemented"); + } + + virtual void emitLogOut() + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/interfaces/module_login_delegate_interface.h b/src-cpp/app/modules/startup/interfaces/module_login_delegate_interface.h new file mode 100644 index 0000000000..d4eb2268ea --- /dev/null +++ b/src-cpp/app/modules/startup/interfaces/module_login_delegate_interface.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace Modules +{ +namespace Startup +{ +class ModuleLoginDelegateInterface +{ +public: + virtual void loginDidLoad() + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/interfaces/module_onboarding_delegate_interface.h b/src-cpp/app/modules/startup/interfaces/module_onboarding_delegate_interface.h new file mode 100644 index 0000000000..4e24fd9e8e --- /dev/null +++ b/src-cpp/app/modules/startup/interfaces/module_onboarding_delegate_interface.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace Modules +{ +namespace Startup +{ +class ModuleOnboardingDelegateInterface +{ +public: + virtual void onboardingDidLoad() + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/interfaces/module_view_delegate_interface.h b/src-cpp/app/modules/startup/interfaces/module_view_delegate_interface.h new file mode 100644 index 0000000000..fac1ef1e3f --- /dev/null +++ b/src-cpp/app/modules/startup/interfaces/module_view_delegate_interface.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace Modules +{ +namespace Startup +{ +class ModuleViewDelegateInterface +{ +public: + virtual void viewDidLoad() + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/controller.cpp b/src-cpp/app/modules/startup/login/controller.cpp new file mode 100644 index 0000000000..f864952ee9 --- /dev/null +++ b/src-cpp/app/modules/startup/login/controller.cpp @@ -0,0 +1,87 @@ +#include "controller.h" +#include "accounts/account.h" +#include "accounts/service_interface.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "signals.h" +#include +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +Controller::Controller(ModuleControllerDelegateInterface* d, + // keychainService + Accounts::ServiceInterface* accountsService, + QObject* parent) + : QObject(parent) + , m_accountsService(accountsService) + , m_delegate(d) +{ } + +void Controller::init() +{ + QObject::connect(Signals::Manager::instance(), &Signals::Manager::nodeLogin, this, &Controller::onLogin); + // keychainServiceSuccess see src-cpp/app/modules/startup/login/controller.nim line 43 + // keychainServiceError see src-cpp/app/modules/startup/login/controller.nim line 47 +} + +void Controller::onLogin(Signals::NodeSignal signal) +{ + if(!signal.error.isEmpty()) + { + m_delegate->emitAccountLoginError(signal.error); + } +} + +QVector Controller::getOpenedAccounts() +{ + return m_accountsService->openedAccounts(); +} + +Accounts::AccountDto Controller::getSelectedAccount() +{ + auto openedAccounts = Controller::getOpenedAccounts(); + foreach(const Accounts::AccountDto& acc, openedAccounts) + { + if(acc.keyUid == m_selectedAccountKeyUid) + { + return acc; + } + } +} + +void Controller::setSelectedAccountKeyUid(QString keyUid) +{ + m_selectedAccountKeyUid = keyUid; + + // Dealing with Keychain is the MacOS only feature + // if(not defined(macosx)): + // return + + // let selectedAccount = self.getSelectedAccount() + // singletonInstance.localAccountSettings.setFileName(selectedAccount.name) + + // let value = singletonInstance.localAccountSettings.getStoreToKeychainValue() + // if (value != LS_VALUE_STORE): + // return + + // self.keychainService.tryToObtainPassword(selectedAccount.name) +} + +void Controller::login(QString password) +{ + auto selectedAccount = Controller::getSelectedAccount(); + auto error = m_accountsService->login(selectedAccount, password); + if(!error.isEmpty()) + { + m_delegate->emitAccountLoginError(error); + } +} + +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/controller.h b/src-cpp/app/modules/startup/login/controller.h new file mode 100644 index 0000000000..b6203e9fdf --- /dev/null +++ b/src-cpp/app/modules/startup/login/controller.h @@ -0,0 +1,41 @@ +#pragma once + +#include "accounts/account.h" +#include "accounts/service_interface.h" +#include "controller_interface.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "signals.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +class Controller : public QObject, ControllerInterface +{ + Q_OBJECT + +public: + Controller(ModuleControllerDelegateInterface* d, + // keychainService, + Accounts::ServiceInterface* accountsService, + QObject* parent = nullptr); + void init() override; + QVector getOpenedAccounts() override; + Accounts::AccountDto getSelectedAccount(); + void setSelectedAccountKeyUid(QString keyUid) override; + void login(QString password) override; + void onLogin(Signals::NodeSignal signal); + +private: + // Keychain::m_keychainService + Accounts::ServiceInterface* m_accountsService; + ModuleControllerDelegateInterface* m_delegate; + QString m_selectedAccountKeyUid; +}; +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/controller_interface.h b/src-cpp/app/modules/startup/login/controller_interface.h new file mode 100644 index 0000000000..0d1c1c0f4d --- /dev/null +++ b/src-cpp/app/modules/startup/login/controller_interface.h @@ -0,0 +1,36 @@ +#pragma once + +#include "accounts/account.h" + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ + +// Abstract class for any input/interaction with this module. + +class ControllerInterface +{ +public: + virtual void init() + { + throw std::domain_error("Not implemented"); + } + virtual QVector getOpenedAccounts() + { + throw std::domain_error("Not implemented"); + } + virtual void setSelectedAccountKeyUid(QString keyUid) + { + throw std::domain_error("Not implemented"); + } + virtual void login(QString password) + { + throw std::domain_error("Not implemented"); + } +}; +} // namespace Login +} // namespace Startup +} // namespace Modules diff --git a/src-cpp/app/modules/startup/login/interfaces/module_controller_delegate_interface.h b/src-cpp/app/modules/startup/login/interfaces/module_controller_delegate_interface.h new file mode 100644 index 0000000000..821be14cb3 --- /dev/null +++ b/src-cpp/app/modules/startup/login/interfaces/module_controller_delegate_interface.h @@ -0,0 +1,34 @@ +#pragma once + +#include "accounts/service_interface.h" +#include "app_controller_delegate.h" +#include "controller.h" +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +class ModuleControllerDelegateInterface +{ +public: + virtual void emitAccountLoginError(QString error) + { + throw std::domain_error("Not implemented"); + } + + virtual void emitObtainingPasswordError(QString errorDescription) + { + throw std::domain_error("Not implemented"); + } + + virtual void emitObtainingPasswordSuccess(QString password) + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Login +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/interfaces/module_view_delegate_interface.h b/src-cpp/app/modules/startup/login/interfaces/module_view_delegate_interface.h new file mode 100644 index 0000000000..df767fc247 --- /dev/null +++ b/src-cpp/app/modules/startup/login/interfaces/module_view_delegate_interface.h @@ -0,0 +1,32 @@ +#pragma once +#include "../item.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +class ModuleViewDelegateInterface +{ +public: + virtual void viewDidLoad() + { + throw std::domain_error("Not implemented"); + } + + virtual void setSelectedAccount(Item item) + { + throw std::domain_error("Not implemented"); + } + + virtual void login(QString password) + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Login +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/item.cpp b/src-cpp/app/modules/startup/login/item.cpp new file mode 100644 index 0000000000..249ac4a9af --- /dev/null +++ b/src-cpp/app/modules/startup/login/item.cpp @@ -0,0 +1,41 @@ +#include "item.h" +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +Item::Item() { } + +Item::Item(QString name, QString identicon, QString thumbnailImage, QString largeImage, QString keyUid) + : m_name(name) + , m_identicon(identicon) + , m_thumbnailImage(thumbnailImage) + , m_largeImage(largeImage) + , m_keyUid(keyUid) +{ } +QString Item::getName() +{ + return m_name; +} +QString Item::getIdenticon() +{ + return m_identicon; +} +QString Item::getThumbnailImage() +{ + return m_thumbnailImage; +} +QString Item::getLargeImage() +{ + return m_largeImage; +} +QString Item::getKeyUid() +{ + return m_keyUid; +} +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/item.h b/src-cpp/app/modules/startup/login/item.h new file mode 100644 index 0000000000..1c2fefd62f --- /dev/null +++ b/src-cpp/app/modules/startup/login/item.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +class Item +{ +private: + QString m_name; + QString m_identicon; + QString m_thumbnailImage; + QString m_largeImage; + QString m_keyUid; + +public: + Item(); + Item(QString name, QString identicon, QString thumbnailImage, QString largeImage, QString keyUid); + QString getName(); + QString getIdenticon(); + QString getThumbnailImage(); + QString getLargeImage(); + QString getKeyUid(); +}; +} // namespace Login +} // namespace Startup +} // namespace Modules diff --git a/src-cpp/app/modules/startup/login/model.cpp b/src-cpp/app/modules/startup/login/model.cpp new file mode 100644 index 0000000000..c9cd8ed508 --- /dev/null +++ b/src-cpp/app/modules/startup/login/model.cpp @@ -0,0 +1,75 @@ +#include "model.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +Model::Model(QObject* parent) + : QAbstractListModel(parent) +{ } + +QHash Model::roleNames() const +{ + QHash roles; + roles[Name] = "username"; + roles[Identicon] = "identicon"; + roles[ThumbnailImage] = "thumbnailImage"; + roles[LargeImage] = "largeImage"; + roles[KeyUid] = "keyUid"; + return roles; +} + +int Model::rowCount(const QModelIndex& parent = QModelIndex()) const +{ + return m_items.size(); +} + +QVariant Model::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + { + return QVariant(); + } + + if(index.row() < 0 || index.row() > m_items.size()) + { + return QVariant(); + } + + Item item = m_items[index.row()]; + + switch(role) + { + case Name: return QVariant(item.getName()); + case Identicon: return QVariant(item.getIdenticon()); + case ThumbnailImage: return QVariant(item.getThumbnailImage()); + case LargeImage: return QVariant(item.getLargeImage()); + case KeyUid: return QVariant(item.getKeyUid()); + } + + return QVariant(); +} + +void Model::setItems(QVector items) +{ + beginResetModel(); + m_items = items; + endResetModel(); +} + +Item Model::getItemAtIndex(int index) +{ + if(index < 0 || index >= m_items.size()) + { + return Item(); + } + + return m_items[index]; +} +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/model.h b/src-cpp/app/modules/startup/login/model.h new file mode 100644 index 0000000000..44ac558d96 --- /dev/null +++ b/src-cpp/app/modules/startup/login/model.h @@ -0,0 +1,41 @@ +#pragma once + +#include "item.h" +#include +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +class Model : public QAbstractListModel +{ + Q_OBJECT + +public: + enum ModelRole + { + Name = Qt::UserRole + 1, + Identicon = Qt::UserRole + 2, + ThumbnailImage = Qt::UserRole + 3, + LargeImage = Qt::UserRole + 4, + KeyUid = Qt::UserRole + 5 + }; + + explicit Model(QObject* parent = nullptr); + + QHash roleNames() const; + virtual int rowCount(const QModelIndex&) const; + virtual QVariant data(const QModelIndex& index, int role) const; + void setItems(QVector items); + Item getItemAtIndex(int index); + +private: + QVector m_items; +}; +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/module.cpp b/src-cpp/app/modules/startup/login/module.cpp new file mode 100644 index 0000000000..a536e5e3c8 --- /dev/null +++ b/src-cpp/app/modules/startup/login/module.cpp @@ -0,0 +1,115 @@ +#include "module.h" +#include "../interfaces/module_login_delegate_interface.h" +#include "accounts/account.h" +#include "accounts/service_interface.h" +#include "controller.h" +#include "singleton.h" +#include "view.h" +#include +#include +#include +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +Module::Module(Modules::Startup::ModuleLoginDelegateInterface* d, + // keychainService + Accounts::ServiceInterface* accountsService) +{ + m_delegate = d; + m_controller = new Controller(this, accountsService); + m_view = new View(this); + m_moduleLoaded = false; +} + +Module::~Module() +{ + delete m_controller; + delete m_view; +} + +void Module::extractImages(Accounts::AccountDto account, QString &thumbnailImage, QString &largeImage) +{ + foreach(const Accounts::Image& img, account.images) + { + if(img.imgType == "thumbnail") + { + thumbnailImage = img.uri; + } + else if(img.imgType == "large") + { + largeImage = img.uri; + } + } +} + +void Module::load() +{ + Global::Singleton::instance()->engine()->rootContext()->setContextProperty("loginModule", m_view); + m_controller->init(); + m_view->load(); + + QVector openedAccounts = m_controller->getOpenedAccounts(); + if(openedAccounts.size() > 0) + { + QVector items; + foreach(const Accounts::AccountDto& acc, openedAccounts) + { + QString thumbnailImage; + QString largeImage; + Module::extractImages(acc, thumbnailImage, largeImage); + items << Item(acc.name, acc.identicon, thumbnailImage, largeImage, acc.keyUid); + } + + m_view->setModelItems(items); + + // set the first account as slected one + m_controller->setSelectedAccountKeyUid(items[0].getKeyUid()); + Module::setSelectedAccount(items[0]); + } +} + +bool Module::isLoaded() +{ + return m_moduleLoaded; +} + +void Module::viewDidLoad() +{ + m_moduleLoaded = true; + m_delegate->loginDidLoad(); +} + +void Module::setSelectedAccount(Item item) +{ + m_controller->setSelectedAccountKeyUid(item.getKeyUid()); + m_view->setSelectedAccount(item); +} + +void Module::login(QString password) +{ + m_controller->login(password); +} + +void Module::emitAccountLoginError(QString error) +{ + m_view->emitAccountLoginError(error); +} + +void Module::emitObtainingPasswordError(QString errorDescription) +{ + m_view->emitObtainingPasswordError(errorDescription); +} + +void Module::emitObtainingPasswordSuccess(QString password) +{ + m_view->emitObtainingPasswordSuccess(password); +} +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/module.h b/src-cpp/app/modules/startup/login/module.h new file mode 100644 index 0000000000..dce1964ec7 --- /dev/null +++ b/src-cpp/app/modules/startup/login/module.h @@ -0,0 +1,46 @@ +#pragma once + +#include "../interfaces/module_login_delegate_interface.h" +#include "accounts/generated_account.h" +#include "accounts/service_interface.h" +#include "controller.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "interfaces/module_view_delegate_interface.h" +#include "item.h" +#include "module_access_interface.h" +#include "view.h" +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +class Module : public ModuleAccessInterface, ModuleControllerDelegateInterface, ModuleViewDelegateInterface +{ +private: + Modules::Startup::ModuleLoginDelegateInterface* m_delegate; + View* m_view; + Controller* m_controller; + bool m_moduleLoaded; + +public: + Module(Modules::Startup::ModuleLoginDelegateInterface* d, + // keychainService + Accounts::ServiceInterface* accountsService); + ~Module(); + void extractImages(Accounts::AccountDto account, QString &thumbnailImage, QString &largeImage); + void load() override; + bool isLoaded() override; + void viewDidLoad() override; + void setSelectedAccount(Item item) override; + void login(QString password) override; + void setupAccountError(); + void emitAccountLoginError(QString error) override; + void emitObtainingPasswordError(QString errorDescription) override; + void emitObtainingPasswordSuccess(QString password) override; +}; +}; // namespace Login +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/module_access_interface.h b/src-cpp/app/modules/startup/login/module_access_interface.h new file mode 100644 index 0000000000..9d6d2703cf --- /dev/null +++ b/src-cpp/app/modules/startup/login/module_access_interface.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +class ModuleAccessInterface +{ +public: + virtual void load() + { + throw std::domain_error("Not implemented"); + } + + virtual bool isLoaded() + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Login +}; // namespace Startup +}; // namespace Modules diff --git a/src-cpp/app/modules/startup/login/selected_account.cpp b/src-cpp/app/modules/startup/login/selected_account.cpp new file mode 100644 index 0000000000..c3c27fb566 --- /dev/null +++ b/src-cpp/app/modules/startup/login/selected_account.cpp @@ -0,0 +1,44 @@ +#include "selected_account.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +SelectedAccount::SelectedAccount(QObject* parent) + : QObject(parent) +{ } + +void SelectedAccount::setSelectedAccountData(Item item) +{ + m_item = item; +} +QString SelectedAccount::getName() +{ + return m_item.getName(); +} +QString SelectedAccount::getIdenticon() +{ + return m_item.getIdenticon(); +} + +QString SelectedAccount::getKeyUid() +{ + return m_item.getKeyUid(); +} + +QString SelectedAccount::getThumbnailImage() +{ + return m_item.getThumbnailImage(); +} + +QString SelectedAccount::getLargeImage() +{ + return m_item.getLargeImage(); +} +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/selected_account.h b/src-cpp/app/modules/startup/login/selected_account.h new file mode 100644 index 0000000000..09a149ec83 --- /dev/null +++ b/src-cpp/app/modules/startup/login/selected_account.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include "item.h" + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ + +class SelectedAccount : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString username READ getName CONSTANT) + Q_PROPERTY(QString identicon READ getIdenticon CONSTANT) + Q_PROPERTY(QString keyUid READ getKeyUid CONSTANT) + Q_PROPERTY(QString thumbnailImage READ getThumbnailImage CONSTANT) + Q_PROPERTY(QString largeImage READ getLargeImage CONSTANT) + +public: + explicit SelectedAccount(QObject* parent = nullptr); + +private: + Item m_item; + +public slots: + void setSelectedAccountData(Item item); + QString getName(); + QString getIdenticon(); + QString getKeyUid(); + QString getThumbnailImage(); + QString getLargeImage(); +}; +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/view.cpp b/src-cpp/app/modules/startup/login/view.cpp new file mode 100644 index 0000000000..b29e37317b --- /dev/null +++ b/src-cpp/app/modules/startup/login/view.cpp @@ -0,0 +1,83 @@ +#include "view.h" +#include "interfaces/module_view_delegate_interface.h" +#include "model.h" +#include "selected_account.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ +View::View(ModuleViewDelegateInterface* d, QObject* parent) + : QObject(parent) +{ + m_delegate = d; + m_model = new Model(); + m_selectedAccount = new SelectedAccount(); +} + +View::~View() +{ + delete m_model; + delete m_selectedAccount; +} + +void View::load() +{ + m_delegate->viewDidLoad(); +} + +Model* View::getModel() +{ + return m_model; +} + +SelectedAccount* View::getSelectedAccount() +{ + return m_selectedAccount; +} + +void View::setSelectedAccount(Item item) +{ + m_selectedAccount->setSelectedAccountData(item); + View::selectedAccountChanged(); +} + +void View::setSelectedAccountByIndex(int index) +{ + Item item = m_model->getItemAtIndex(index); + m_delegate->setSelectedAccount(item); +} + +void View::setModelItems(QVector accounts) +{ + m_model->setItems(accounts); + View::modelChanged(); +} + +void View::login(QString password) +{ + m_delegate->login(password); +} + +void View::emitAccountLoginError(QString error) +{ + emit View::accountLoginError(error); +} + +void View::emitObtainingPasswordError(QString errorDescription) +{ + emit View::obtainingPasswordError(errorDescription); +} + +void View::emitObtainingPasswordSuccess(QString password) +{ + emit View::obtainingPasswordSuccess(password); +} + +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/login/view.h b/src-cpp/app/modules/startup/login/view.h new file mode 100644 index 0000000000..6d959490af --- /dev/null +++ b/src-cpp/app/modules/startup/login/view.h @@ -0,0 +1,53 @@ +#pragma once + +#include "interfaces/module_view_delegate_interface.h" +#include "model.h" +#include "selected_account.h" +#include +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Login +{ + +class View : public QObject +{ + Q_OBJECT + Q_PROPERTY(SelectedAccount* selectedAccount READ getSelectedAccount NOTIFY selectedAccountChanged) + Q_PROPERTY(Model* accountsModel READ getModel NOTIFY modelChanged) + +public: + explicit View(ModuleViewDelegateInterface* d, QObject* parent = nullptr); + ~View(); + void load(); + +signals: + void selectedAccountChanged(); + void modelChanged(); + void accountLoginError(QString error); + void obtainingPasswordError(QString errorDescription); + void obtainingPasswordSuccess(QString password); + +private: + ModuleViewDelegateInterface* m_delegate; + Model* m_model; + SelectedAccount* m_selectedAccount; + +public slots: + Model* getModel(); + SelectedAccount* getSelectedAccount(); + void setSelectedAccount(Item item); + void setSelectedAccountByIndex(int index); + void setModelItems(QVector accounts); + void login(QString password); + void emitAccountLoginError(QString error); + void emitObtainingPasswordError(QString errorDescription); + void emitObtainingPasswordSuccess(QString password); +}; +} // namespace Login +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/module.cpp b/src-cpp/app/modules/startup/module.cpp new file mode 100644 index 0000000000..0c466496c3 --- /dev/null +++ b/src-cpp/app/modules/startup/module.cpp @@ -0,0 +1,102 @@ +#include "module.h" +#include "accounts/service_interface.h" +#include "controller.h" +#include "modules/startup/login/module.h" +#include "modules/startup/onboarding/module.h" +#include "singleton.h" +#include "view.h" +#include +#include +#include +#include + +namespace Modules +{ +namespace Startup +{ +Module::Module(AppControllerDelegate* d, + /*keychainService,*/ + Accounts::ServiceInterface* accountsService) + +{ + m_delegate = d; + m_controller = new Controller(this, accountsService); + m_view = new View(this); + + // Submodules + m_onboardingModule = new Modules::Startup::Onboarding::Module(this, accountsService); + m_loginModule = new Modules::Startup::Login::Module(this, /*keychainService, */ accountsService); +} + +Module::~Module() +{ + delete m_controller; + delete m_view; + delete m_onboardingModule; + delete m_loginModule; +} + +void Module::load() +{ + Global::Singleton::instance()->engine()->rootContext()->setContextProperty("startupModule", m_view); + m_controller->init(); + m_view->load(); + + AppState initialAppState(AppState::OnboardingState); + if(!m_controller->shouldStartWithOnboardingScreen()) + { + initialAppState = AppState::LoginState; + } + + m_view->setAppState(initialAppState); + + m_onboardingModule->load(); + m_loginModule->load(); +} + +void Module::checkIfModuleDidLoad() +{ + if(!m_onboardingModule->isLoaded()) + { + return; + } + + if(!m_loginModule->isLoaded()) + { + return; + } + + m_delegate->startupDidLoad(); +} + +void Module::viewDidLoad() +{ + Module::checkIfModuleDidLoad(); +} + +void Module::onboardingDidLoad() +{ + Module::checkIfModuleDidLoad(); +} + +void Module::loginDidLoad() +{ + Module::checkIfModuleDidLoad(); +} + +void Module::userLoggedIn() +{ + m_delegate->userLoggedIn(); +} + +void Module::moveToAppState() +{ + m_view->setAppState(AppState::MainAppState); +} + +void Module::emitLogOut() +{ + m_view->emitLogOut(); +} +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/module.h b/src-cpp/app/modules/startup/module.h new file mode 100644 index 0000000000..14d97c35cd --- /dev/null +++ b/src-cpp/app/modules/startup/module.h @@ -0,0 +1,48 @@ +#pragma once + +#include "accounts/service_interface.h" +#include "app_controller_delegate.h" +#include "controller.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "interfaces/module_login_delegate_interface.h" +#include "interfaces/module_onboarding_delegate_interface.h" +#include "interfaces/module_view_delegate_interface.h" +#include "login/module_access_interface.h" +#include "module_access_interface.h" +#include "onboarding/module_access_interface.h" +#include "view.h" +#include + +namespace Modules +{ +namespace Startup +{ +class Module : public ModuleAccessInterface, + ModuleOnboardingDelegateInterface, + ModuleLoginDelegateInterface, + ModuleControllerDelegateInterface, + ModuleViewDelegateInterface +{ +private: + AppControllerDelegate* m_delegate; + View* m_view; + Controller* m_controller; + + Modules::Startup::Onboarding::ModuleAccessInterface* m_onboardingModule; + Modules::Startup::Login::ModuleAccessInterface* m_loginModule; + +public: + Module(AppControllerDelegate* d, + /*keychainService,*/ Accounts::ServiceInterface* accountsService); + ~Module(); + void load() override; + void checkIfModuleDidLoad(); + void viewDidLoad() override; + void onboardingDidLoad(); + void loginDidLoad(); + void userLoggedIn() override; + void moveToAppState() override; + void emitLogOut() override; +}; +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/module_access_interface.h b/src-cpp/app/modules/startup/module_access_interface.h new file mode 100644 index 0000000000..8ded7fc7fa --- /dev/null +++ b/src-cpp/app/modules/startup/module_access_interface.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace Modules +{ +namespace Startup +{ +class ModuleAccessInterface +{ +public: + virtual void load() + { + throw std::domain_error("Not implemented"); + } + + virtual void moveToAppState() + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Startup +}; // namespace Modules diff --git a/src-cpp/app/modules/startup/onboarding/controller.cpp b/src-cpp/app/modules/startup/onboarding/controller.cpp new file mode 100644 index 0000000000..a15552c9c4 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/controller.cpp @@ -0,0 +1,80 @@ +#include "controller.h" +#include "accounts/generated_account.h" +#include "accounts/service_interface.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "signals.h" +#include +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +Controller::Controller(ModuleControllerDelegateInterface* d, + Accounts::ServiceInterface* accountsService, + QObject* parent) + : QObject(parent) + , m_accountsService(accountsService) + , m_delegate(d) +{ } + +void Controller::init() +{ + QObject::connect(Signals::Manager::instance(), &Signals::Manager::nodeLogin, this, &Controller::onLogin); +} + +void Controller::onLogin(Signals::NodeSignal signal) +{ + if(!signal.error.isEmpty()) + { + m_delegate->setupAccountError(); + } +} + +QVector Controller::getGeneratedAccounts() +{ + return m_accountsService->generatedAccounts(); +} + +Accounts::GeneratedAccountDto Controller::getImportedAccount() +{ + return m_accountsService->getImportedAccount(); +} + +void Controller::setSelectedAccountByIndex(int index) +{ + auto accounts = Controller::getGeneratedAccounts(); + m_selectedAccountId = accounts[index].id; +} + +void Controller::storeSelectedAccountAndLogin(QString password) +{ + if(!m_accountsService->setupAccount(m_selectedAccountId, password)) + { + m_delegate->setupAccountError(); + } +} + +QString Controller::validateMnemonic(QString mnemonic) +{ + return m_accountsService->validateMnemonic(mnemonic); +} + +void Controller::importMnemonic(QString mnemonic) +{ + if(m_accountsService->importMnemonic(mnemonic)) + { + m_selectedAccountId = Controller::getImportedAccount().id; + m_delegate->importAccountSuccess(); + } + else + { + m_delegate->importAccountError(); + } +} +} // namespace Onboarding +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/controller.h b/src-cpp/app/modules/startup/onboarding/controller.h new file mode 100644 index 0000000000..e48d14be82 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/controller.h @@ -0,0 +1,41 @@ +#pragma once + +#include "accounts/generated_account.h" +#include "accounts/service_interface.h" +#include "controller_interface.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "signals.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +class Controller : public QObject, ControllerInterface +{ + Q_OBJECT + +public: + Controller(ModuleControllerDelegateInterface* d, + Accounts::ServiceInterface* accountsService, + QObject* parent = nullptr); + void init() override; + QVector getGeneratedAccounts() override; + Accounts::GeneratedAccountDto getImportedAccount() override; + void setSelectedAccountByIndex(int index) override; + void storeSelectedAccountAndLogin(QString password) override; + QString validateMnemonic(QString mnemonic) override; + void importMnemonic(QString mnemonic) override; + void onLogin(Signals::NodeSignal signal); + +private: + Accounts::ServiceInterface* m_accountsService; + ModuleControllerDelegateInterface* m_delegate; + QString m_selectedAccountId; +}; +} // namespace Onboarding +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/controller_interface.h b/src-cpp/app/modules/startup/onboarding/controller_interface.h new file mode 100644 index 0000000000..1a236c2a4a --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/controller_interface.h @@ -0,0 +1,48 @@ +#pragma once + +#include "accounts/generated_account.h" + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ + +// Abstract class for any input/interaction with this module. + +class ControllerInterface +{ +public: + virtual void init() + { + throw std::domain_error("Not implemented"); + } + virtual QVector getGeneratedAccounts() + { + throw std::domain_error("Not implemented"); + } + virtual void setSelectedAccountByIndex(int index) + { + throw std::domain_error("Not implemented"); + } + virtual void storeSelectedAccountAndLogin(QString password) + { + throw std::domain_error("Not implemented"); + } + virtual Accounts::GeneratedAccountDto getImportedAccount() + { + throw std::domain_error("Not implemented"); + } + virtual QString validateMnemonic(QString mnemonic) + { + throw std::domain_error("Not implemented"); + } + virtual void importMnemonic(QString mnemonic) + { + throw std::domain_error("Not implemented"); + } +}; +} // namespace Onboarding +} // namespace Startup +} // namespace Modules diff --git a/src-cpp/app/modules/startup/onboarding/interfaces/module_controller_delegate_interface.h b/src-cpp/app/modules/startup/onboarding/interfaces/module_controller_delegate_interface.h new file mode 100644 index 0000000000..030237bdc4 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/interfaces/module_controller_delegate_interface.h @@ -0,0 +1,34 @@ +#pragma once + +#include "accounts/service_interface.h" +#include "app_controller_delegate.h" +#include "controller.h" +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +class ModuleControllerDelegateInterface +{ +public: + virtual void setupAccountError() + { + throw std::domain_error("Not implemented"); + } + + virtual void importAccountError() + { + throw std::domain_error("Not implemented"); + } + + virtual void importAccountSuccess() + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Onboarding +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/interfaces/module_view_delegate_interface.h b/src-cpp/app/modules/startup/onboarding/interfaces/module_view_delegate_interface.h new file mode 100644 index 0000000000..6f8199a627 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/interfaces/module_view_delegate_interface.h @@ -0,0 +1,47 @@ +#pragma once +#include "accounts/generated_account.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +class ModuleViewDelegateInterface +{ +public: + virtual void viewDidLoad() + { + throw std::domain_error("Not implemented"); + } + + virtual void setSelectedAccountByIndex(int index) + { + throw std::domain_error("Not implemented"); + } + + virtual void storeSelectedAccountAndLogin(QString password) + { + throw std::domain_error("Not implemented"); + } + + virtual Accounts::GeneratedAccountDto getImportedAccount() + { + throw std::domain_error("Not implemented"); + } + + virtual QString validateMnemonic(QString mnemonic) + { + throw std::domain_error("Not implemented"); + } + + virtual void importMnemonic(QString mnemonic) + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Onboarding +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/item.cpp b/src-cpp/app/modules/startup/onboarding/item.cpp new file mode 100644 index 0000000000..61e84de2f3 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/item.cpp @@ -0,0 +1,39 @@ +#include "item.h" +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +Item::Item(QString id, QString alias, QString identicon, QString address, QString keyUid) + : m_id(id) + , m_alias(alias) + , m_identicon(identicon) + , m_address(address) + , m_keyUid(keyUid) +{ } +QString Item::getId() +{ + return m_id; +} +QString Item::getAlias() +{ + return m_alias; +} +QString Item::getIdenticon() +{ + return m_identicon; +} +QString Item::getAddress() +{ + return m_address; +} +QString Item::getKeyUid() +{ + return m_keyUid; +} +} // namespace Onboarding +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/item.h b/src-cpp/app/modules/startup/onboarding/item.h new file mode 100644 index 0000000000..7e9c4a2509 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/item.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +class Item +{ +private: + QString m_id; + QString m_alias; + QString m_identicon; + QString m_address; + QString m_keyUid; + +public: + Item(QString id, QString alias, QString identicon, QString address, QString keyUid); + QString getId(); + QString getAlias(); + QString getIdenticon(); + QString getAddress(); + QString getKeyUid(); +}; +} // namespace Onboarding +} // namespace Startup +} // namespace Modules diff --git a/src-cpp/app/modules/startup/onboarding/model.cpp b/src-cpp/app/modules/startup/onboarding/model.cpp new file mode 100644 index 0000000000..d27e1ab614 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/model.cpp @@ -0,0 +1,66 @@ +#include "model.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +Model::Model(QObject* parent) + : QAbstractListModel(parent) +{ } + +QHash Model::roleNames() const +{ + QHash roles; + roles[Id] = "accountId"; + roles[Alias] = "username"; + roles[Identicon] = "identicon"; + roles[Address] = "address"; + roles[KeyUid] = "keyUid"; + return roles; +} + +int Model::rowCount(const QModelIndex& parent = QModelIndex()) const +{ + return m_items.size(); +} + +QVariant Model::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + { + return QVariant(); + } + + if(index.row() < 0 || index.row() > m_items.size()) + { + return QVariant(); + } + + Item item = m_items[index.row()]; + + switch(role) + { + case Id: return QVariant(item.getId()); + case Alias: return QVariant(item.getAlias()); + case Identicon: return QVariant(item.getIdenticon()); + case Address: return QVariant(item.getAddress()); + case KeyUid: return QVariant(item.getKeyUid()); + } + + return QVariant(); +} + +void Model::setItems(QVector items) +{ + beginResetModel(); + m_items = items; + endResetModel(); +} + +} // namespace Onboarding +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/model.h b/src-cpp/app/modules/startup/onboarding/model.h new file mode 100644 index 0000000000..edcd698dbd --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/model.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include "item.h" + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +class Model : public QAbstractListModel +{ + Q_OBJECT + +public: + enum ModelRole + { + Id = Qt::UserRole + 1, + Alias = Qt::UserRole + 2, + Identicon = Qt::UserRole + 3, + Address = Qt::UserRole + 4, + KeyUid = Qt::UserRole + 5 + }; + + explicit Model(QObject* parent = nullptr); + + QHash roleNames() const; + virtual int rowCount(const QModelIndex&) const; + virtual QVariant data(const QModelIndex& index, int role) const; + void setItems(QVector items); + +private: + QVector m_items; +}; +} // namespace Onboarding +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/module.cpp b/src-cpp/app/modules/startup/onboarding/module.cpp new file mode 100644 index 0000000000..fe341b9f71 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/module.cpp @@ -0,0 +1,101 @@ +#include "module.h" +#include "accounts/generated_account.h" +#include "accounts/service_interface.h" +#include "../interfaces/module_onboarding_delegate_interface.h" +#include "controller.h" +#include "singleton.h" +#include "view.h" +#include +#include +#include +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +Module::Module(Modules::Startup::ModuleOnboardingDelegateInterface* d, Accounts::ServiceInterface* accountsService) +{ + m_delegate = d; + m_controller = new Controller(this, accountsService); + m_view = new View(this); + m_moduleLoaded = false; +} + +Module::~Module() +{ + delete m_controller; + delete m_view; +} + +void Module::load() +{ + Global::Singleton::instance()->engine()->rootContext()->setContextProperty("onboardingModule", m_view); + m_controller->init(); + m_view->load(); + + QVector gAcc = m_controller->getGeneratedAccounts(); + QVector accounts; + foreach(const Accounts::GeneratedAccountDto& acc, gAcc) + { + accounts << Item(acc.id, acc.alias, acc.identicon, acc.address, acc.keyUid); + } + + m_view->setAccountList(accounts); +} + +bool Module::isLoaded() +{ + return m_moduleLoaded; +} + +void Module::viewDidLoad() +{ + m_moduleLoaded = true; + m_delegate->onboardingDidLoad(); +} + +void Module::setSelectedAccountByIndex(int index) +{ + m_controller->setSelectedAccountByIndex(index); +} + +void Module::storeSelectedAccountAndLogin(QString password) +{ + m_controller->storeSelectedAccountAndLogin(password); +} +void Module::setupAccountError() +{ + m_view->setupAccountError(); +} + +Accounts::GeneratedAccountDto Module::getImportedAccount() +{ + return m_controller->getImportedAccount(); +} + +QString Module::validateMnemonic(QString mnemonic) +{ + return m_controller->validateMnemonic(mnemonic); +} + +void Module::importMnemonic(QString mnemonic) +{ + m_controller->importMnemonic(mnemonic); +} + +void Module::importAccountError() +{ + m_view->importAccountError(); +} + +void Module::importAccountSuccess() +{ + m_view->importAccountSuccess(); +} +} // namespace Onboarding +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/module.h b/src-cpp/app/modules/startup/onboarding/module.h new file mode 100644 index 0000000000..6637d9fde2 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/module.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../interfaces/module_onboarding_delegate_interface.h" +#include "accounts/generated_account.h" +#include "accounts/service_interface.h" +#include "controller.h" +#include "interfaces/module_controller_delegate_interface.h" +#include "interfaces/module_view_delegate_interface.h" +#include "module_access_interface.h" +#include "view.h" +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +class Module : public ModuleAccessInterface, ModuleControllerDelegateInterface, ModuleViewDelegateInterface +{ +private: + Modules::Startup::ModuleOnboardingDelegateInterface* m_delegate; + View* m_view; + Controller* m_controller; + bool m_moduleLoaded; + +public: + Module(Modules::Startup::ModuleOnboardingDelegateInterface* d, Accounts::ServiceInterface* accountsService); + ~Module(); + void load() override; + bool isLoaded() override; + void viewDidLoad() override; + void setSelectedAccountByIndex(int index) override; + void storeSelectedAccountAndLogin(QString password) override; + void setupAccountError() override; + Accounts::GeneratedAccountDto getImportedAccount() override; + QString validateMnemonic(QString mnemonic) override; + void importMnemonic(QString mnemonic) override; + void importAccountError() override; + void importAccountSuccess() override; +}; +}; // namespace Onboarding +}; // namespace Startup +}; // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/module_access_interface.h b/src-cpp/app/modules/startup/onboarding/module_access_interface.h new file mode 100644 index 0000000000..1627196bf0 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/module_access_interface.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +class ModuleAccessInterface +{ +public: + virtual void load() + { + throw std::domain_error("Not implemented"); + } + + virtual bool isLoaded() + { + throw std::domain_error("Not implemented"); + } +}; +}; // namespace Onboarding +}; // namespace Startup +}; // namespace Modules diff --git a/src-cpp/app/modules/startup/onboarding/view.cpp b/src-cpp/app/modules/startup/onboarding/view.cpp new file mode 100644 index 0000000000..79c317767c --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/view.cpp @@ -0,0 +1,93 @@ +#include "view.h" +#include "interfaces/module_view_delegate_interface.h" +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ +View::View(ModuleViewDelegateInterface* d, QObject* parent) + : QObject(parent) +{ + m_delegate = d; + m_model = new Model(); +} + +View::~View() +{ + delete m_model; +} + +void View::load() +{ + m_delegate->viewDidLoad(); +} + +Model* View::getModel() +{ + return m_model; +} + +void View::setAccountList(QVector accounts) +{ + m_model->setItems(accounts); + View::modelChanged(); +} + +QString View::getImportedAccountIdenticon() +{ + return m_delegate->getImportedAccount().identicon; +} + +QString View::getImportedAccountAlias() +{ + return m_delegate->getImportedAccount().alias; +} + +QString View::getImportedAccountAddress() +{ + return m_delegate->getImportedAccount().address; +} + +void View::setSelectedAccountByIndex(int index) +{ + m_delegate->setSelectedAccountByIndex(index); +} + +void View::storeSelectedAccountAndLogin(QString password) +{ + m_delegate->storeSelectedAccountAndLogin(password); +} + +void View::setupAccountError() +{ + View::accountSetupError(); +} + +QString View::validateMnemonic(QString mnemonic) +{ + return m_delegate->validateMnemonic(mnemonic); +} + +void View::importMnemonic(QString mnemonic) +{ + m_delegate->importMnemonic(mnemonic); +} + +void View::importAccountError() +{ + // In QML we can connect to this signal and notify a user + // before refactoring we didn't have this signal + View::accountImportError(); +} + +void View::importAccountSuccess() +{ + View::importedAccountChanged(); +} +} // namespace Onboarding +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/onboarding/view.h b/src-cpp/app/modules/startup/onboarding/view.h new file mode 100644 index 0000000000..8665a16934 --- /dev/null +++ b/src-cpp/app/modules/startup/onboarding/view.h @@ -0,0 +1,55 @@ +#pragma once + +#include "interfaces/module_view_delegate_interface.h" +#include "model.h" +#include +#include +#include + +namespace Modules +{ +namespace Startup +{ +namespace Onboarding +{ + +class View : public QObject +{ + Q_OBJECT + Q_PROPERTY(Model* accountsModel READ getModel NOTIFY modelChanged) + Q_PROPERTY(QString importedAccountIdenticon READ getImportedAccountIdenticon NOTIFY importedAccountChanged) + Q_PROPERTY(QString importedAccountAlias READ getImportedAccountAlias NOTIFY importedAccountChanged) + Q_PROPERTY(QString importedAccountAddress READ getImportedAccountAddress NOTIFY importedAccountChanged) + +public: + explicit View(ModuleViewDelegateInterface* d, QObject* parent = nullptr); + ~View(); + void load(); + +signals: + void modelChanged(); + void importedAccountChanged(); + void accountSetupError(); + void accountImportError(); + +private: + ModuleViewDelegateInterface* m_delegate; + Model* m_model; + +public slots: + Model* getModel(); + void setAccountList(QVector accounts); + QString getImportedAccountIdenticon(); + QString getImportedAccountAlias(); + QString getImportedAccountAddress(); + void setSelectedAccountByIndex(int index); + void storeSelectedAccountAndLogin(QString password); + QString validateMnemonic(QString mnemonic); + void importMnemonic(QString mnemonic); + void importAccountError(); + void setupAccountError(); + void importAccountSuccess(); +}; +} // namespace Onboarding +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/view.cpp b/src-cpp/app/modules/startup/view.cpp new file mode 100644 index 0000000000..d2557dbd48 --- /dev/null +++ b/src-cpp/app/modules/startup/view.cpp @@ -0,0 +1,45 @@ +#include "view.h" +#include "interfaces/module_view_delegate_interface.h" +#include + +namespace Modules +{ +namespace Startup +{ + +View::View(ModuleViewDelegateInterface* d, QObject* parent) + : QObject(parent) + , m_appState(AppState::OnboardingState) +{ + m_delegate = d; +} + +void View::load() +{ + // In some point, here, we will setup some exposed main module related things. + m_delegate->viewDidLoad(); +} + +int View::getAppState() +{ + return static_cast(m_appState); +} + +void View::setAppState(AppState state) +{ + if(m_appState == state) + { + return; + } + + m_appState = state; + appStateChanged(static_cast(m_appState)); +} + +void View::emitLogOut() +{ + logOut(); +} + +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app/modules/startup/view.h b/src-cpp/app/modules/startup/view.h new file mode 100644 index 0000000000..edd6854fa4 --- /dev/null +++ b/src-cpp/app/modules/startup/view.h @@ -0,0 +1,41 @@ +#pragma once + +#include "interfaces/module_view_delegate_interface.h" +#include + +namespace Modules +{ +namespace Startup +{ +enum AppState +{ + OnboardingState = 0, + LoginState = 1, + MainAppState = 2 + // TODO: is Pending +}; + +class View : public QObject +{ + Q_OBJECT + Q_PROPERTY(int appState READ getAppState NOTIFY appStateChanged) + +public: + explicit View(ModuleViewDelegateInterface* d, QObject* parent = nullptr); + void emitLogOut(); + void setAppState(AppState state); + void load(); + +signals: + void appStateChanged(int state); + void logOut(); + +private: + ModuleViewDelegateInterface* m_delegate; + AppState m_appState; + +public slots: + int getAppState(); +}; +} // namespace Startup +} // namespace Modules \ No newline at end of file diff --git a/src-cpp/app_service/CMakeLists.txt b/src-cpp/app_service/CMakeLists.txt new file mode 100644 index 0000000000..5c8cef6616 --- /dev/null +++ b/src-cpp/app_service/CMakeLists.txt @@ -0,0 +1,34 @@ +add_library(app_service + constants.cpp + service/accounts/dto/account.cpp + service/accounts/dto/generated_account.cpp + service/accounts/service.cpp +) + +target_include_directories(app_service + PUBLIC + include +) + +# default token is a free-tier token with limited capabilities and usage +# limits; setup your own infura key with +# cmake -DINFURA_KEY=infura_key_goes_here .. +if( "${INFURA_KEY}" STREQUAL "") + message("-- Using default Infura key") + file (STRINGS "../../resources/infura_key" INFURA_KEY) +else() + message("-- Using custom Infura key") +endif() + +# Build constants +target_compile_definitions(app_service + PRIVATE + INFURA_KEY="${INFURA_KEY}" +) + +target_link_libraries(app_service + PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Quick + backend +) diff --git a/src-cpp/app_service/constants.cpp b/src-cpp/app_service/constants.cpp new file mode 100644 index 0000000000..e4b5f8422e --- /dev/null +++ b/src-cpp/app_service/constants.cpp @@ -0,0 +1,24 @@ +#include "constants.h" +#include +#include +#include +#include + +// TODO: merge with constants from backend/ + + +QString Constants::applicationPath(QString path) +{ + return QFileInfo(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + path).absoluteFilePath(); +} + +QString Constants::tmpPath(QString path) +{ + return QFileInfo(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + path).absoluteFilePath(); +} + +QString Constants::cachePath(QString path) +{ + return QFileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + path).absoluteFilePath(); +} + diff --git a/src-cpp/app_service/include/accounts/account.h b/src-cpp/app_service/include/accounts/account.h new file mode 100644 index 0000000000..209cc52e53 --- /dev/null +++ b/src-cpp/app_service/include/accounts/account.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +namespace Accounts +{ +class Image +{ +public: + QString keyUid; + QString imgType; + QString uri; + int width; + int height; + int fileSize; + int resizeTarget; +}; + +class AccountDto +{ +public: + QString name; + long timestamp; + QString identicon; + QString keycardPairing; + QString keyUid; + QVector images; + + bool isValid(); +}; + +Image toImage(const QJsonValue jsonObj); + +AccountDto toAccountDto(const QJsonValue jsonObj); +} // namespace Accounts \ No newline at end of file diff --git a/src-cpp/app_service/include/accounts/generated_account.h b/src-cpp/app_service/include/accounts/generated_account.h new file mode 100644 index 0000000000..9bb106aade --- /dev/null +++ b/src-cpp/app_service/include/accounts/generated_account.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace Accounts +{ +class DerivedAccountDetails +{ +public: + QString publicKey; + QString address; + QString derivationPath; +}; + +class DerivedAccounts +{ +public: + DerivedAccountDetails whisper; + DerivedAccountDetails walletRoot; + DerivedAccountDetails defaultWallet; + DerivedAccountDetails eip1581; +}; + +class GeneratedAccountDto +{ +public: + QString id; + QString publicKey; + QString address; + QString keyUid; + QString mnemonic; + DerivedAccounts derivedAccounts; + + // The following two are set additionally. + QString alias; + QString identicon; + + bool isValid(); +}; + +DerivedAccountDetails toDerivedAccountDetails(const QJsonValue jsonObj, QString derivationPath); + +DerivedAccounts toDerivedAccounts(const QJsonObject jsonObj); + +GeneratedAccountDto toGeneratedAccountDto(const QJsonValue jsonObj); +} // namespace Accounts diff --git a/src-cpp/app_service/include/accounts/service.h b/src-cpp/app_service/include/accounts/service.h new file mode 100644 index 0000000000..b0feedba10 --- /dev/null +++ b/src-cpp/app_service/include/accounts/service.h @@ -0,0 +1,72 @@ +#pragma once + +#include "account.h" +#include "generated_account.h" +#include "service_interface.h" +#include +#include + +namespace Accounts +{ + +class Service : public ServiceInterface +{ +private: + QVector m_generatedAccounts; + + bool m_isFirstTimeAccountLogin; + AccountDto m_loggedInAccount; + GeneratedAccountDto m_importedAccount; + +public: + Service(); + + void init() override; + + virtual QVector openedAccounts() override; + + QVector generatedAccounts() override; + + bool setupAccount(QString accountId, QString password) override; + + AccountDto getLoggedInAccount() override; + + GeneratedAccountDto getImportedAccount() override; + + bool isFirstTimeAccountLogin() override; + + QString validateMnemonic(QString mnemonic) override; + + bool importMnemonic(QString mnemonic) override; + + QString login(AccountDto account, QString password) override; + + void clear() override; + + QString generateAlias(QString publicKey) override; + + QString generateIdenticon(QString publicKey) override; + + bool verifyAccountPassword(QString account, QString password) override; + + DerivedAccounts storeDerivedAccounts(QString accountId, QString hashedPassword, QVector paths); + + QJsonObject getAccountDataForAccountId(QString accountId); + + QJsonArray getSubaccountDataForAccountId(QString accountId); + + QJsonObject getAccountSettings(QString accountId, QString installationId); + + QJsonObject getDefaultNodeConfig(QString installationId); + + QJsonObject prepareAccountJsonObject(const GeneratedAccountDto account); + + QJsonArray prepareSubaccountJsonObject(GeneratedAccountDto account); + + QJsonObject prepareAccountSettingsJsonObject(const GeneratedAccountDto account, QString installationId); + + AccountDto saveAccountAndLogin( + QString hashedPassword, QJsonObject account, QJsonArray subaccounts, QJsonObject settings, QJsonObject config); +}; + +} // namespace Accounts \ No newline at end of file diff --git a/src-cpp/app_service/include/accounts/service_interface.h b/src-cpp/app_service/include/accounts/service_interface.h new file mode 100644 index 0000000000..50ca24da42 --- /dev/null +++ b/src-cpp/app_service/include/accounts/service_interface.h @@ -0,0 +1,83 @@ +#pragma once + +#include "../app_service.h" +#include "account.h" +#include "generated_account.h" +#include +#include +#include +#include + +namespace Accounts +{ + +class ServiceInterface : public AppService +{ +public: + virtual QVector openedAccounts() + { + throw std::domain_error("Not implemented"); + } + + virtual QVector generatedAccounts() + { + throw std::domain_error("Not implemented"); + } + + virtual bool setupAccount(QString accountId, QString password) + { + throw std::domain_error("Not implemented"); + } + + virtual AccountDto getLoggedInAccount() + { + throw std::domain_error("Not implemented"); + } + + virtual GeneratedAccountDto getImportedAccount() + { + throw std::domain_error("Not implemented"); + } + + virtual bool isFirstTimeAccountLogin() + { + throw std::domain_error("Not implemented"); + } + + virtual QString validateMnemonic(QString mnemonic) + { + throw std::domain_error("Not implemented"); + } + + virtual bool importMnemonic(QString mnemonic) + { + throw std::domain_error("Not implemented"); + } + + virtual QString login(AccountDto account, QString password) + { + throw std::domain_error("Not implemented"); + } + + virtual void clear() + { + throw std::domain_error("Not implemented"); + } + + virtual QString generateAlias(QString publicKey) + { + throw std::domain_error("Not implemented"); + } + + virtual QString generateIdenticon(QString publicKey) + { + throw std::domain_error("Not implemented"); + } + + virtual bool verifyAccountPassword(QString account, QString password) + { + throw std::domain_error("Not implemented"); + } +}; + +} // namespace Accounts \ No newline at end of file diff --git a/src-cpp/app_service/include/app_service.h b/src-cpp/app_service/include/app_service.h new file mode 100644 index 0000000000..d015e2960d --- /dev/null +++ b/src-cpp/app_service/include/app_service.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class AppService +{ +public: + virtual void init() + { + throw std::domain_error("Not implemented"); + } +}; diff --git a/src-cpp/app_service/include/constants.h b/src-cpp/app_service/include/constants.h new file mode 100644 index 0000000000..50f2fa9a9a --- /dev/null +++ b/src-cpp/app_service/include/constants.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace Constants +{ +namespace Fleet +{ +const QString Prod = "eth.prod"; +const QString Staging = "eth.staging"; +const QString Test = "eth.test"; +const QString WakuV2Prod = "wakuv2.prod"; +const QString WakuV2Test = "wakuv2.test"; +const QString GoWakuTest = "go-waku.test"; +}; // namespace Fleet + +namespace FleetNodes +{ +const QString Bootnodes = "boot"; +const QString Mailservers = "mail"; +const QString Rendezvous = "rendezvous"; +const QString Whisper = "whisper"; +const QString Waku = "waku"; +const QString LibP2P = "libp2p"; +const QString Websocket = "websocket"; +} // namespace FleetNodes + +const QString DefaultNetworkName = "mainnet_rpc"; +//const DEFAULT_NETWORKS_IDS* = @["mainnet_rpc", "testnet_rpc", "rinkeby_rpc", "goerli_rpc", "xdai_rpc", "poa_rpc" ] + + +const QString DataDir = "/data"; +const QString Keystore = "/data/keystore"; + +QString applicationPath(QString path = ""); +QString tmpPath(QString path = ""); +QString cachePath(QString path = ""); + + +} // namespace Constants diff --git a/src-cpp/app_service/include/signing-phrases.h b/src-cpp/app_service/include/signing-phrases.h new file mode 100644 index 0000000000..fb6194b414 --- /dev/null +++ b/src-cpp/app_service/include/signing-phrases.h @@ -0,0 +1,39 @@ +#include +#include + +const std::array phrases{ + "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"}; diff --git a/src-cpp/app_service/service/accounts/dto/account.cpp b/src-cpp/app_service/service/accounts/dto/account.cpp new file mode 100644 index 0000000000..3bc0a5470b --- /dev/null +++ b/src-cpp/app_service/service/accounts/dto/account.cpp @@ -0,0 +1,44 @@ +#include "accounts/account.h" +#include "backend/accounts.h" +#include +#include +#include +#include + +bool Accounts::AccountDto::isValid() +{ + return name.length() > 0 && keyUid.length() > 0; +} + +Accounts::Image Accounts::toImage(const QJsonValue jsonObj) +{ + auto result = Accounts::Image(); + + result.keyUid = jsonObj["keyUid"].toString(); + result.imgType = jsonObj["type"].toString(); + result.uri = jsonObj["uri"].toString(); + result.width = jsonObj["width"].toInt(); + result.height = jsonObj["height"].toInt(); + result.fileSize = jsonObj["fileSize"].toInt(); + result.resizeTarget = jsonObj["resizeTarget"].toInt(); + + return result; +} + +Accounts::AccountDto Accounts::toAccountDto(const QJsonValue jsonObj) +{ + auto result = Accounts::AccountDto(); + + result.name = jsonObj["name"].toString(); + result.timestamp = jsonObj["timestamp"].toInt(); + result.identicon = jsonObj["identicon"].toString(); + result.keycardPairing = jsonObj["keycard-pairing"].toString(); + result.keyUid = jsonObj["key-uid"].toString(); + + foreach(const QJsonValue& value, jsonObj["images"].toArray()) + { + result.images << Accounts::toImage(value); + } + + return result; +} diff --git a/src-cpp/app_service/service/accounts/dto/generated_account.cpp b/src-cpp/app_service/service/accounts/dto/generated_account.cpp new file mode 100644 index 0000000000..ac31e37c14 --- /dev/null +++ b/src-cpp/app_service/service/accounts/dto/generated_account.cpp @@ -0,0 +1,69 @@ +#include "accounts/generated_account.h" +#include "backend/accounts.h" +#include +#include +#include +#include +#include + +bool Accounts::GeneratedAccountDto::isValid() +{ + return id.length() > 0 && publicKey.length() > 0 && address.length() > 0 && keyUid.length() > 0; +} + +Accounts::DerivedAccountDetails Accounts::toDerivedAccountDetails(const QJsonValue jsonObj, QString derivationPath) +{ + // Mapping this DTO is not strightforward since only keys are used for id. We + // handle it a bit different. + auto result = Accounts::DerivedAccountDetails(); + + result.derivationPath = derivationPath; + result.publicKey = jsonObj["publicKey"].toString(); + result.address = jsonObj["address"].toString(); + + return result; +} + +Accounts::DerivedAccounts Accounts::toDerivedAccounts(const QJsonObject jsonObj) +{ + auto result = Accounts::DerivedAccounts(); + foreach(const QString& derivationPath, jsonObj.keys()) + { + QJsonValue derivedObj = jsonObj.value(derivationPath); + if(derivationPath == Backend::Accounts::PATH_WHISPER) + { + result.whisper = Accounts::toDerivedAccountDetails(derivedObj, derivationPath); + } + else if(derivationPath == Backend::Accounts::PATH_WALLET_ROOT) + { + result.walletRoot = Accounts::toDerivedAccountDetails(derivedObj, derivationPath); + } + else if(derivationPath == Backend::Accounts::PATH_DEFAULT_WALLET) + { + result.defaultWallet = Accounts::toDerivedAccountDetails(derivedObj, derivationPath); + } + else if(derivationPath == Backend::Accounts::PATH_EIP_1581) + { + result.eip1581 = Accounts::toDerivedAccountDetails(derivedObj, derivationPath); + } + } + + return result; +} + +Accounts::GeneratedAccountDto Accounts::toGeneratedAccountDto(const QJsonValue jsonObj) +{ + auto result = GeneratedAccountDto(); + + result.id = jsonObj["id"].toString(); + result.address = jsonObj["address"].toString(); + result.keyUid = jsonObj["keyUid"].toString(); + result.mnemonic = jsonObj["mnemonic"].toString(); + result.publicKey = jsonObj["publicKey"].toString(); + if(!jsonObj["derived"].isUndefined()) + { + result.derivedAccounts = Accounts::toDerivedAccounts(jsonObj["derived"].toObject()); + } + + return result; +} \ No newline at end of file diff --git a/src-cpp/app_service/service/accounts/service.cpp b/src-cpp/app_service/service/accounts/service.cpp new file mode 100644 index 0000000000..7a64052c6d --- /dev/null +++ b/src-cpp/app_service/service/accounts/service.cpp @@ -0,0 +1,374 @@ +#include "accounts/service.h" +#include "accounts/account.h" +#include "accounts/generated_account.h" +#include "accounts/service_interface.h" +#include "app_service.h" +#include "backend/accounts.h" +#include "backend/utils.h" +#include "constants.h" +#include "signing-phrases.h" +#include +#include +#include +#include +#include +#include + +namespace Accounts +{ + +Service::Service() + : m_isFirstTimeAccountLogin(false) +{ } + +const QVector PATHS{Backend::Accounts::PATH_WALLET_ROOT, + Backend::Accounts::PATH_EIP_1581, + Backend::Accounts::PATH_WHISPER, + Backend::Accounts::PATH_DEFAULT_WALLET}; + +void Service::init() +{ + auto response = Backend::Accounts::generateAddresses(Accounts::PATHS); + foreach(QJsonValue generatedAddressJson, response.m_result) + { + auto gAcc = toGeneratedAccountDto(generatedAddressJson); + gAcc.alias = generateAlias(gAcc.derivedAccounts.whisper.publicKey); + gAcc.identicon = generateIdenticon(gAcc.derivedAccounts.whisper.publicKey); + m_generatedAccounts << gAcc; + } +} + +QVector Service::openedAccounts() +{ + // try + auto response = Backend::Accounts::openAccounts(Constants::applicationPath(Constants::DataDir)); + QJsonArray multiAccounts = response.m_result; + QVector result; + foreach(const QJsonValue& value, multiAccounts) + { + result << toAccountDto(value); + } + return result; + //} catch(const std::exception& e){ + // error "error: ", methodName="openedAccounts", errName = e.name, errDesription = e.msg +} + +QVector Service::generatedAccounts() +{ + if(m_generatedAccounts.length() == 0) + { + qWarning("There was some issue initiating account service"); + return QVector(); + } + + return m_generatedAccounts; +} + +bool Service::setupAccount(QString accountId, QString password) +{ + //try: + QString installationId(QUuid::createUuid().toString(QUuid::WithoutBraces)); + QJsonObject accountData(Service::getAccountDataForAccountId(accountId)); + QJsonArray subAccountData(Service::getSubaccountDataForAccountId(accountId)); + QJsonObject settings(Service::getAccountSettings(accountId, installationId)); + QJsonObject nodeConfig(Service::getDefaultNodeConfig(installationId)); + + // if(accountDataJson.isNil or subaccountDataJson.isNil or settingsJson.isNil or + // nodeConfigJson.isNil): + //let description = "at least one json object is not prepared well" + //error "error: ", methodName="setupAccount", errDesription = description + //return false + + QString hashedPassword(Backend::Utils::hashString(password)); + + Service::storeDerivedAccounts(accountId, hashedPassword, PATHS); + + m_loggedInAccount = Service::saveAccountAndLogin(hashedPassword, accountData, subAccountData, settings, nodeConfig); + + return Service::getLoggedInAccount().isValid(); + + //except Exception as e: + // error "error: ", methodName="setupAccount", errName = e.name, errDesription = e.msg + //return false*/ +} + +AccountDto Service::getLoggedInAccount() +{ + return m_loggedInAccount; +} + +GeneratedAccountDto Service::getImportedAccount() +{ + return m_importedAccount; +} + +bool Service::isFirstTimeAccountLogin() +{ + return m_isFirstTimeAccountLogin; +} + +QString Service::validateMnemonic(QString mnemonic) +{ + // TODO: + return ""; +} + +bool Service::importMnemonic(QString mnemonic) +{ + // TODO: + return false; +} + +QString Service::login(AccountDto account, QString password) +{ + //try: + QString hashedPassword(Backend::Utils::hashString(password)); + + QString thumbnailImage; + QString largeImage; + + foreach(const Accounts::Image& img, account.images) + { + if(img.imgType == "thumbnail") + { + thumbnailImage = img.uri; + } + else if(img.imgType == "large") + { + largeImage = img.uri; + } + } + + auto response = Backend::Accounts::login( + account.name, account.keyUid, hashedPassword, account.identicon, thumbnailImage, largeImage); + // TODO: check response for errors + + qDebug() << "Account logged in"; + + m_loggedInAccount = account; + + return ""; + + //except Exception as e: + //error "error: ", methodName="setupAccount", errName = e.name, errDesription = e.msg + //return e.msg +} + +void Service::clear() +{ + m_generatedAccounts.clear(); + m_loggedInAccount = Accounts::AccountDto(); + m_importedAccount = Accounts::GeneratedAccountDto(); + m_isFirstTimeAccountLogin = false; +} + +QString Service::generateAlias(QString publicKey) +{ + return Backend::Accounts::generateAlias(publicKey).m_result; +} + +QString Service::generateIdenticon(QString publicKey) +{ + return Backend::Accounts::generateIdenticon(publicKey).m_result; +} + +bool Service::verifyAccountPassword(QString account, QString password) +{ + // TODO: + return false; +} + +DerivedAccounts Service::storeDerivedAccounts(QString accountId, QString hashedPassword, QVector paths) +{ + try + { + auto response = Backend::Accounts::storeDerivedAccounts(accountId, hashedPassword, paths); + return toDerivedAccounts(response.m_result); + } + catch(Backend::RpcException& e) + { + qWarning() << e.what(); + return DerivedAccounts(); // TODO: should it return empty? + } +} + +Accounts::AccountDto Service::saveAccountAndLogin( + QString hashedPassword, QJsonObject account, QJsonArray subaccounts, QJsonObject settings, QJsonObject config) +{ + //try: + auto response = Backend::Accounts::saveAccountAndLogin(hashedPassword, account, subaccounts, settings, config); + + m_isFirstTimeAccountLogin = true; + return toAccountDto(account); + + // except Exception as e: + // error "error: ", methodName="saveAccountAndLogin", errName = e.name, errDesription = e.msg +} + +QJsonObject Service::prepareAccountJsonObject(const GeneratedAccountDto account) +{ + return QJsonObject{{"name", account.alias}, + {"address", account.address}, + {"photo-path", account.identicon}, + {"identicon", account.identicon}, + {"key-uid", account.keyUid}, + {"keycard-pairing", QJsonValue()}}; +} + +QJsonObject Service::getAccountDataForAccountId(QString accountId) +{ + + foreach(const GeneratedAccountDto& acc, m_generatedAccounts) + { + if(acc.id == accountId) + { + return Service::prepareAccountJsonObject(acc); + } + } + + if(m_importedAccount.isValid()) + { + if(m_importedAccount.id == accountId) + { + return Service::prepareAccountJsonObject(m_importedAccount); + } + } +} + +QJsonArray Service::prepareSubaccountJsonObject(GeneratedAccountDto account) +{ + return QJsonArray{QJsonObject{{"public-key", account.derivedAccounts.defaultWallet.publicKey}, + {"address", account.derivedAccounts.defaultWallet.address}, + {"color", "#4360df"}, + {"wallet", true}, + {"path", Backend::Accounts::PATH_DEFAULT_WALLET}, + {"name", "Status account"}}, + QJsonObject{{"public-key", account.derivedAccounts.whisper.publicKey}, + {"address", account.derivedAccounts.whisper.address}, + {"path", Backend::Accounts::PATH_WHISPER}, + {"name", account.alias}, + {"identicon", account.identicon}, + {"chat", true}}}; +} + +QJsonArray Service::getSubaccountDataForAccountId(QString accountId) +{ + foreach(const GeneratedAccountDto& acc, m_generatedAccounts) + { + + if(acc.id == accountId) + { + return prepareSubaccountJsonObject(acc); + } + } + if(m_importedAccount.isValid()) + { + if(m_importedAccount.id == accountId) + { + return prepareSubaccountJsonObject(m_importedAccount); + } + } +} + +QString generateSigningPhrase(int count) +{ + QStringList words; + for(int i = 0; i < count; i++) + { + words.append(phrases[QRandomGenerator::global()->bounded(static_cast(phrases.size()))]); + } + return words.join(" "); +} + +QJsonObject Service::prepareAccountSettingsJsonObject(const GeneratedAccountDto account, QString installationId) +{ + QFile defaultNetworks(":/resources/default-networks.json"); + defaultNetworks.open(QIODevice::ReadOnly); + + QString defaultNetworksContent = defaultNetworks.readAll().replace("%INFURA_KEY%", INFURA_KEY); + 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}, + {"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::DefaultNetworkName, QJsonArray{"SNT"}}}}, + {"appearance", 0}, + {"networks/current-network", Constants::DefaultNetworkName}, + {"installation-id", installationId}}; +} + +QJsonObject Service::getAccountSettings(QString accountId, QString installationId) +{ + foreach(const GeneratedAccountDto& acc, m_generatedAccounts) + + if(acc.id == accountId) + { + return Service::prepareAccountSettingsJsonObject(acc, installationId); + } + + if(m_importedAccount.isValid()) + { + if(m_importedAccount.id == accountId) + { + return Service::prepareAccountSettingsJsonObject(m_importedAccount, installationId); + } + } +} + +QJsonArray getNodes(const QJsonObject fleet, QString nodeType) +{ + auto nodes = fleet[nodeType].toObject(); + QJsonArray result; + for(auto it = nodes.begin(); it != nodes.end(); ++it) + result << *it; + return result; +} + +QJsonObject Service::getDefaultNodeConfig(QString installationId) +{ + QFile nodeConfig(":/resources/node-config.json"); + nodeConfig.open(QIODevice::ReadOnly); + + QString nodeConfigContent = nodeConfig.readAll(); + + nodeConfigContent = nodeConfigContent.replace("%INSTALLATIONID%", installationId); + nodeConfigContent = nodeConfigContent.replace("%INFURA_KEY%", INFURA_KEY); + + QJsonObject nodeConfigJson = QJsonDocument::fromJson(nodeConfigContent.toUtf8()).object(); + + QFile fleets(":/resources/fleets.json"); + fleets.open(QIODevice::ReadOnly); + QJsonObject fleetsJson = QJsonDocument::fromJson(fleets.readAll()).object()["fleets"].toObject(); + + auto fleet = fleetsJson[Constants::Fleet::Prod].toObject(); + + QJsonObject clusterConfig = nodeConfigJson["ClusterConfig"].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; +} +} // namespace Accounts diff --git a/src-cpp/backend/CMakeLists.txt b/src-cpp/backend/CMakeLists.txt new file mode 100644 index 0000000000..75bb57781f --- /dev/null +++ b/src-cpp/backend/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(backend + accounts.cpp + types.cpp + utils.cpp +) + +target_include_directories(backend + PUBLIC + include/ +) + +target_link_libraries(backend + PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Quick + statusgo_shared + +) diff --git a/src-cpp/backend/accounts.cpp b/src-cpp/backend/accounts.cpp new file mode 100644 index 0000000000..d021824a50 --- /dev/null +++ b/src-cpp/backend/accounts.cpp @@ -0,0 +1,97 @@ +#include "backend/accounts.h" +#include "backend/types.h" +#include "backend/utils.h" +#include +#include +#include +#include +#include +#include +#include "libstatus.h" + +const int NUMBER_OF_ADDRESSES_TO_GENERATE = 5; +const int MNEMONIC_PHRASE_LENGTH = 12; + +Backend::RpcResponse Backend::Accounts::generateAddresses(QVector paths) +{ + QJsonObject payload{{"n", NUMBER_OF_ADDRESSES_TO_GENERATE}, + {"mnemonicPhraseLength", MNEMONIC_PHRASE_LENGTH}, + {"bip32Passphrase", ""}, + {"paths", Utils::toJsonArray(paths)} + + }; + const char* result = MultiAccountGenerateAndDeriveAddresses(Utils::jsonToStr(payload).toUtf8().data()); + return Backend::RpcResponse(result, QJsonDocument::fromJson(result).array()); +} + +Backend::RpcResponse Backend::Accounts::generateIdenticon(QString publicKey) +{ + if(!publicKey.isEmpty()) + { + auto identicon = QString(Identicon(publicKey.toUtf8().data())); + return Backend::RpcResponse(identicon, identicon); + } + else + { + throw Backend::RpcException("publicKey can't be empty1"); + } +} + +Backend::RpcResponse Backend::Accounts::generateAlias(QString publicKey) +{ + if(!publicKey.isEmpty()) + { + auto alias = QString(GenerateAlias(publicKey.toUtf8().data())); + return Backend::RpcResponse(alias, alias); + } + else + { + throw Backend::RpcException("publicKey can't be empty2"); + } +} + +Backend::RpcResponse +Backend::Accounts::storeDerivedAccounts(QString id, QString hashedPassword, QVector paths) +{ + QJsonObject payload{{"accountID", id}, {"paths", Utils::toJsonArray(paths)}, {"password", hashedPassword}}; + auto result = MultiAccountStoreDerivedAccounts(Utils::jsonToStr(payload).toUtf8().data()); + auto obj = QJsonDocument::fromJson(result).object(); + Backend::Utils::throwOnError(obj); + return Backend::RpcResponse(result, obj); +} + +Backend::RpcResponse Backend::Accounts::saveAccountAndLogin( + QString hashedPassword, QJsonObject account, QJsonArray subaccounts, QJsonObject settings, QJsonObject nodeConfig) +{ + auto result = SaveAccountAndLogin(Utils::jsonToStr(account).toUtf8().data(), + hashedPassword.toUtf8().data(), + Utils::jsonToStr(settings).toUtf8().data(), + Utils::jsonToStr(nodeConfig).toUtf8().data(), + Utils::jsonToStr(subaccounts).toUtf8().data()); + auto obj = QJsonDocument::fromJson(result).object(); + Backend::Utils::throwOnError(obj); + return Backend::RpcResponse(result, obj); +} + +Backend::RpcResponse Backend::Accounts::openAccounts(QString path) +{ + const char* result = OpenAccounts(path.toUtf8().data()); + auto resp = Backend::RpcResponse(result, QJsonDocument::fromJson(result).array()); + return resp; +} + +Backend::RpcResponse Backend::Accounts::login( + QString name, QString keyUid, QString hashedPassword, QString identicon, QString thumbnail, QString large) +{ + QJsonObject payload{{"name", name}, {"key-uid", keyUid}, {"identityImage", QJsonValue()}, {"identicon", identicon}}; + + if(!thumbnail.isEmpty() && !large.isEmpty()) + { + payload["identityImage"] = QJsonObject{{"thumbnail", thumbnail}, {"large", large}}; + } + + auto result = Login(Utils::jsonToStr(payload).toUtf8().data(), hashedPassword.toUtf8().data()); + auto obj = QJsonDocument::fromJson(result).object(); + Backend::Utils::throwOnError(obj); + return Backend::RpcResponse(result, obj); +} \ No newline at end of file diff --git a/src-cpp/backend/include/backend/accounts.h b/src-cpp/backend/include/backend/accounts.h new file mode 100644 index 0000000000..945931e06f --- /dev/null +++ b/src-cpp/backend/include/backend/accounts.h @@ -0,0 +1,39 @@ +#pragma once + +#include "backend/types.h" +#include +#include +#include + +namespace Backend +{ +namespace Accounts +{ +const QString ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; + +const QString PATH_WALLET_ROOT = "m/44'/60'/0'/0"; +// EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived +const QString PATH_EIP_1581 = "m/43'/60'/1581'"; +// BIP44-0 Wallet key, the default wallet key +const QString PATH_DEFAULT_WALLET = PATH_WALLET_ROOT + "/0"; +// EIP1581 Chat Key 0, the default whisper key +const QString PATH_WHISPER = PATH_EIP_1581 + "/0'/0"; + +RpcResponse generateAddresses(QVector paths); + +RpcResponse generateIdenticon(QString publicKey); + +RpcResponse generateAlias(QString publicKey); + +RpcResponse storeDerivedAccounts(QString accountId, QString hashedPassword, QVector paths); + +RpcResponse saveAccountAndLogin( + QString hashedPassword, QJsonObject account, QJsonArray subaccounts, QJsonObject settings, QJsonObject nodeConfig); + +RpcResponse openAccounts(QString path); + +RpcResponse +login(QString name, QString keyUid, QString hashedPassword, QString identicon, QString thumbnail, QString large); + +} // namespace Accounts +} // namespace Backend \ No newline at end of file diff --git a/src-cpp/backend/include/backend/types.h b/src-cpp/backend/include/backend/types.h new file mode 100644 index 0000000000..27892cd778 --- /dev/null +++ b/src-cpp/backend/include/backend/types.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +using namespace std; + +namespace Backend +{ + +const QString GENERATED = "generated"; +const QString SEED = "seed"; +const QString KEY = "key"; +const QString WATCH = "watch"; + +struct RpcException : public std::exception +{ +private: + std::string m_message; +public: + explicit RpcException(const std::string& message); + const char* what() const throw(); +}; + +class RpcError +{ +public: + int m_code; + QString m_message; + + friend ostream& operator<<(ostream& os, Backend::RpcError& r); +}; + +template + +class RpcResponse +{ +public: + QString m_jsonrpc; + T m_result; + int m_id; + RpcError m_error; + +public: + RpcResponse(QString jsonrpc, T result) + : m_jsonrpc(jsonrpc) + , m_result(result) + { } +}; + +} // namespace Backend diff --git a/src-cpp/backend/include/backend/utils.h b/src-cpp/backend/include/backend/utils.h new file mode 100644 index 0000000000..cbeddd7eb8 --- /dev/null +++ b/src-cpp/backend/include/backend/utils.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include + +namespace Backend +{ +class Utils +{ +public: + static QString hashString(QString str); + static QString jsonToStr(QJsonObject obj); + static QString jsonToStr(QJsonArray arr); + static QJsonArray toJsonArray(const QVector& value); + static QVector toStringVector(const QJsonArray& arr); + static void throwOnError(QJsonObject response); +}; +} // namespace Backend diff --git a/src-cpp/backend/types.cpp b/src-cpp/backend/types.cpp new file mode 100644 index 0000000000..412cec0cbc --- /dev/null +++ b/src-cpp/backend/types.cpp @@ -0,0 +1,19 @@ +#include "backend/types.h" +#include + +using namespace std; + +ostream& operator<<(ostream& os, const Backend::RpcError& r) +{ + return (os << "RpcError(\n code: " << r.m_code << "\n message: " << r.m_message.toStdString() << "\n)" + << std::endl); +} + +Backend::RpcException::RpcException(const std::string& message) + : m_message(message) +{ } + +const char* Backend::RpcException::what() const throw() +{ + return m_message.c_str(); +} \ No newline at end of file diff --git a/src-cpp/backend/utils.cpp b/src-cpp/backend/utils.cpp new file mode 100644 index 0000000000..5c696bbbfe --- /dev/null +++ b/src-cpp/backend/utils.cpp @@ -0,0 +1,51 @@ +#include "backend/utils.h" +#include "backend/types.h" +#include +#include +#include +#include +#include +#include +#include + +QJsonArray Backend::Utils::toJsonArray(const QVector& value) +{ + QJsonArray array; + for(auto& v : value) + array << v; + return array; +} + +QString Backend::Utils::jsonToStr(QJsonObject obj) +{ + QJsonDocument doc(obj); + return QString::fromUtf8(doc.toJson()); +} + +QString Backend::Utils::jsonToStr(QJsonArray arr) +{ + QJsonDocument doc(arr); + return QString::fromUtf8(doc.toJson()); +} + +QVector Backend::Utils::toStringVector(const QJsonArray& arr) +{ + QVector result; + foreach(const QJsonValue& value, arr) + { + result << value.toString(); + } + return result; +} + +QString Backend::Utils::hashString(QString str) +{ + return "0x" + QString::fromUtf8(QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Keccak_256).toHex()); +} + +void Backend::Utils::throwOnError(QJsonObject response) { + if(!response["error"].isUndefined() && !response["error"].toString().isEmpty()){ + qWarning() << "RpcException: " << response["error"].toString(); + throw Backend::RpcException(response["error"].toString().toStdString()); + } +} \ No newline at end of file diff --git a/src-cpp/constants.cpp b/src-cpp/constants.cpp new file mode 100644 index 0000000000..65ef470c62 --- /dev/null +++ b/src-cpp/constants.cpp @@ -0,0 +1,41 @@ +#include "constants.h" +#include +#include +#include +#include +#include + +// TODO: merge with constants from backend/ + + +QString Constants::applicationPath(QString path) +{ + return QFileInfo(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + path).absoluteFilePath(); +} + +QString Constants::tmpPath(QString path) +{ + return QFileInfo(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + path).absoluteFilePath(); +} + +QString Constants::cachePath(QString path) +{ + return QFileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + path).absoluteFilePath(); +} + +bool Constants::ensureDirectories() +{ + if(Constants::applicationPath().isEmpty()) + { + QDir d{Constants::applicationPath()}; + if(!d.mkpath(d.absolutePath())) + { + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Warning); + msgBox.setText("Cannot determine storage location"); + msgBox.exec(); + return false; + } + } + return true; +} diff --git a/src-cpp/constants.h b/src-cpp/constants.h new file mode 100644 index 0000000000..0ab6ebe6d5 --- /dev/null +++ b/src-cpp/constants.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace Constants +{ +const QString DataDir = "/data"; +const QString Keystore = "/data/keystore"; + +QString applicationPath(QString path = ""); +QString tmpPath(QString path = ""); +QString cachePath(QString path = ""); +bool ensureDirectories(); + +} // namespace Constants diff --git a/src-cpp/dotherside/CMakeLists.txt b/src-cpp/dotherside/CMakeLists.txt new file mode 100644 index 0000000000..d051ae3e36 --- /dev/null +++ b/src-cpp/dotherside/CMakeLists.txt @@ -0,0 +1,30 @@ +if(APPLE) + file(GLOB_RECURSE SOURCES *.cpp *.mm) +else() + file(GLOB_RECURSE SOURCES *.cpp) +endif() + +add_library(DOtherSide + ${SOURCES}) + +if(APPLE) + target_include_directories(DOtherSide + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../../bottles/hunspell/include) +else() + target_include_directories(DOtherSide + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) +endif() + +target_link_libraries(DOtherSide + PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::Gui +) + +include_directories( + # Add here any vendor folder + #../../vendor/vendor_name_goes_here/ +) \ No newline at end of file diff --git a/src-cpp/dotherside/DOtherSide.cpp b/src-cpp/dotherside/DOtherSide.cpp new file mode 100644 index 0000000000..214bcf60b0 --- /dev/null +++ b/src-cpp/dotherside/DOtherSide.cpp @@ -0,0 +1,12 @@ +#include "DOtherSide.h" +#include "SpellChecker.h" +#include "StatusSyntaxHighlighter.h" +#include "StatusWindow.h" + +void DOtherSide::registerMetaTypes() +{ + qRegisterMetaType>(); + qmlRegisterType("DotherSide", 0, 1, "StatusWindow"); + qmlRegisterType("DotherSide", 0, 1, "StatusSyntaxHighlighter"); + qmlRegisterType("DotherSide", 0, 1, "SpellChecker"); +} diff --git a/src-cpp/dotherside/DOtherSide.h b/src-cpp/dotherside/DOtherSide.h new file mode 100644 index 0000000000..9301ec12f9 --- /dev/null +++ b/src-cpp/dotherside/DOtherSide.h @@ -0,0 +1,6 @@ +#pragma once + +class DOtherSide { + public: + static void registerMetaTypes(); +}; diff --git a/src-cpp/dotherside/DOtherSideSingleInstance.dcpp b/src-cpp/dotherside/DOtherSideSingleInstance.dcpp new file mode 100644 index 0000000000..762c9f5001 --- /dev/null +++ b/src-cpp/dotherside/DOtherSideSingleInstance.dcpp @@ -0,0 +1,63 @@ +#include "DOtherSide/DOtherSideSingleInstance.h" + +#include +#include + +namespace { + const int ReadWriteTimeoutMs = 1000; +} + +SingleInstance::SingleInstance(const QString &uniqueName, const QString &eventStr, QObject *parent) + : QObject(parent) + , m_localServer(new QLocalServer(this)) +{ + QString socketName = uniqueName; + +#ifndef Q_OS_WIN + socketName = QString("/tmp/%1").arg(socketName); +#endif + + QLocalSocket localSocket; + localSocket.connectToServer(socketName); + + // the first instance start will be delayed by this timeout (ms) to ensure there are no other instances. + // note: this is an ad-hoc timeout value selected based on prior experience. + if (!localSocket.waitForConnected(100)) { + connect(m_localServer, &QLocalServer::newConnection, this, &SingleInstance::handleNewConnection); + // on *nix a crashed process will leave /tmp/xyz file preventing to start a new server. + // therefore, if we were unable to connect, then we assume the server died and we need to clean up. + // p.s. on Windows, this function does nothing. + QLocalServer::removeServer(socketName); + if (!m_localServer->listen(socketName)) { + qWarning() << "QLocalServer::listen(" << socketName << ") failed"; + } + } else if (!eventStr.isEmpty()) { + localSocket.write(eventStr.toUtf8() + '\n'); + localSocket.waitForBytesWritten(ReadWriteTimeoutMs); + } +} + +SingleInstance::~SingleInstance() +{ + if (m_localServer->isListening()) { + m_localServer->close(); + } +} + +bool SingleInstance::isFirstInstance() const +{ + return m_localServer->isListening(); +} + +void SingleInstance::handleNewConnection() +{ + emit secondInstanceDetected(); + + auto socket = m_localServer->nextPendingConnection(); + if (socket->waitForReadyRead(ReadWriteTimeoutMs) && socket->canReadLine()) { + auto event = socket->readLine(); + emit eventReceived(QString::fromUtf8(event)); + } + + socket->deleteLater(); +} diff --git a/src-cpp/dotherside/DOtherSideSingleInstance.h b/src-cpp/dotherside/DOtherSideSingleInstance.h new file mode 100644 index 0000000000..f958036da7 --- /dev/null +++ b/src-cpp/dotherside/DOtherSideSingleInstance.h @@ -0,0 +1,32 @@ +#ifndef SINGLEINSTANCE_H +#define SINGLEINSTANCE_H + +#include + +class QLocalServer; + +class SingleInstance : public QObject +{ + Q_OBJECT + +public: + // uniqueName - the name of named pipe + // eventStr - optional event to send if another instance is detected + explicit SingleInstance(const QString &uniqueName, const QString &eventStr, QObject *parent = nullptr); + ~SingleInstance() override; + + bool isFirstInstance() const; + +signals: + void secondInstanceDetected(); + void eventReceived(const QString &eventStr); + +private slots: + void handleNewConnection(); + +private: + QLocalServer *m_localServer; +}; + + +#endif // SINGLEINSTANCE_H diff --git a/src-cpp/dotherside/SpellChecker.cpp b/src-cpp/dotherside/SpellChecker.cpp new file mode 100644 index 0000000000..54f9fa1bee --- /dev/null +++ b/src-cpp/dotherside/SpellChecker.cpp @@ -0,0 +1,185 @@ +#include "SpellChecker.h" + +#ifdef Q_OS_MACOS + #include "hunspell/hunspell.hxx" +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include + +SpellChecker::SpellChecker(QObject *parent) + : QObject(parent) +#ifdef Q_OS_MACOS + , m_hunspell(nullptr) +#endif + , m_userDict("userDict_") +{ + +} + +SpellChecker::~SpellChecker() +{ +#ifdef Q_OS_MACOS + delete m_hunspell; +#endif +} + +bool SpellChecker::spell(const QString &word) +{ +#ifdef Q_OS_MACOS + return m_hunspell->spell(m_codec->fromUnicode(word).toStdString()); +#else + return true; +#endif +} + +bool SpellChecker::isInit() const +{ +#ifdef Q_OS_MACOS + return !m_hunspell; +#else + return true; +#endif +} + +void SpellChecker::initHunspell() +{ +#ifdef Q_OS_MACOS + if (m_hunspell) { + delete m_hunspell; + } + + QString dictFile = QGuiApplication::applicationDirPath() + "/dictionaries/" + m_lang + "/index.dic"; + QString affixFile = QGuiApplication::applicationDirPath() + "/dictionaries/" + m_lang + "/index.aff"; + QByteArray dictFilePathBA = dictFile.toLocal8Bit(); + QByteArray affixFilePathBA = affixFile.toLocal8Bit(); + m_hunspell = new Hunspell(affixFilePathBA.constData(), + dictFilePathBA.constData()); + + // detect encoding analyzing the SET option in the affix file + auto encoding = QStringLiteral("ISO8859-15"); + QFile _affixFile(affixFile); + if (_affixFile.open(QIODevice::ReadOnly)) { + QTextStream stream(&_affixFile); + QRegularExpression enc_detector( + QStringLiteral("^\\s*SET\\s+([A-Z0-9\\-]+)\\s*"), + QRegularExpression::CaseInsensitiveOption); + QString sLine; + QRegularExpressionMatch match; + while (!stream.atEnd()) { + sLine = stream.readLine(); + if (sLine.isEmpty()) { continue; } + match = enc_detector.match(sLine); + if (match.hasMatch()) { + encoding = match.captured(1); + qDebug() << "Encoding set to " + encoding; + break; + } + } + _affixFile.close(); + } + m_codec = QTextCodec::codecForName(encoding.toLatin1().constData()); + + QString userDict = m_userDict + m_lang + ".txt"; + + if (!userDict.isEmpty()) { + QFile userDictonaryFile(userDict); + if (userDictonaryFile.open(QIODevice::ReadOnly)) { + QTextStream stream(&userDictonaryFile); + for (QString word = stream.readLine(); + !word.isEmpty(); + word = stream.readLine()) + ignoreWord(word); + userDictonaryFile.close(); + } else { + qWarning() << "User dictionary in " << userDict + << "could not be opened"; + } + } else { + qDebug() << "User dictionary not set."; + } +#endif +} + +QVariantList SpellChecker::suggest(const QString &word) +{ + int numSuggestions = 0; + QVariantList suggestions; +#ifdef Q_OS_MACOS + std::vector wordlist; + wordlist = m_hunspell->suggest(m_codec->fromUnicode(word).toStdString()); + + numSuggestions = static_cast(wordlist.size()); + if (numSuggestions > 0) { + suggestions.reserve(numSuggestions); + for (int i = 0; i < numSuggestions; i++) { + suggestions << m_codec->toUnicode( + QByteArray::fromStdString(wordlist[i])); + } + } +#endif + + return suggestions; +} + +void SpellChecker::ignoreWord(const QString &word) +{ +#ifdef Q_OS_MACOS + m_hunspell->add(m_codec->fromUnicode(word).constData()); +#endif +} + +void SpellChecker::addToUserWordlist(const QString &word) +{ +#ifdef Q_OS_MACOS + QString userDict = m_userDict + m_lang + ".txt"; + if (!userDict.isEmpty()) { + QFile userDictonaryFile(userDict); + if (userDictonaryFile.open(QIODevice::Append)) { + QTextStream stream(&userDictonaryFile); + stream << word << "\n"; + userDictonaryFile.close(); + } else { + qWarning() << "User dictionary in " << userDict + << "could not be opened for appending a new word"; + } + } else { + qDebug() << "User dictionary not set."; + } +#endif +} + +const QString& SpellChecker::lang() const +{ + return m_lang; +} + +void SpellChecker::setLang(const QString& lang) +{ + if (m_lang != lang) { + m_lang = lang; + initHunspell(); + emit langChanged(); + } +} + +const QString& SpellChecker::userDict() const +{ + return m_userDict; +} + +void SpellChecker::setUserDict(const QString& userDict) +{ + if (m_userDict != userDict) { + m_userDict = userDict; + emit userDictChanged(); + } +} diff --git a/src-cpp/dotherside/SpellChecker.h b/src-cpp/dotherside/SpellChecker.h new file mode 100644 index 0000000000..3c425f5ddb --- /dev/null +++ b/src-cpp/dotherside/SpellChecker.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef Q_OS_MACOS +class Hunspell; +#endif +class QTextCodec; + +class SpellChecker : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString lang READ lang WRITE setLang NOTIFY langChanged) + Q_PROPERTY(QString userDict READ userDict WRITE setUserDict NOTIFY userDictChanged) + +public: + explicit SpellChecker(QObject *parent = nullptr); + ~SpellChecker(); + + Q_INVOKABLE bool spell(const QString& word); + Q_INVOKABLE QVariantList suggest(const QString &word); + Q_INVOKABLE void ignoreWord(const QString &word); + Q_INVOKABLE void addToUserWordlist(const QString &word); + Q_INVOKABLE bool isInit() const; + + const QString& lang() const; + void setLang(const QString& lang); + + const QString& userDict() const; + void setUserDict(const QString& userDict); + +signals: + void langChanged(); + void userDictChanged(); + +private: + void initHunspell(); + +private: + QString m_lang; + QString m_userDict; + + QQuickTextDocument *m_document; +#ifdef Q_OS_MACOS + Hunspell *m_hunspell; +#endif + QTextCodec *m_codec; +}; diff --git a/src-cpp/dotherside/StatusSyntaxHighlighter.cpp b/src-cpp/dotherside/StatusSyntaxHighlighter.cpp new file mode 100644 index 0000000000..8ceedd98c1 --- /dev/null +++ b/src-cpp/dotherside/StatusSyntaxHighlighter.cpp @@ -0,0 +1,67 @@ +#include "StatusSyntaxHighlighter.h" +#include + +StatusSyntaxHighlighter::StatusSyntaxHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) +{ + HighlightingRule rule; + +//BOLD + singlelineBoldFormat.setFontWeight(QFont::Bold); + rule.pattern = QRegularExpression(QStringLiteral("\\*\\*(.*?)\\*\\*")); + rule.format = singlelineBoldFormat; + highlightingRules.append(rule); +//BOLD + +//ITALIC + singleLineItalicFormat.setFontItalic(true); + rule.pattern = QRegularExpression(QStringLiteral("\\*(.*?)\\*")); + rule.format = singleLineItalicFormat; + highlightingRules.append(rule); +//ITALIC + +//CODE + singlelineCodeBlockFormat.setFontFamily("Roboto Mono"); + rule.pattern = QRegularExpression(QStringLiteral("\\`(.*?)\\`")); + rule.format = singlelineCodeBlockFormat; + highlightingRules.append(rule); +//CODE + +//STRIKETHROUGH + singleLineStrikeThroughFormat.setFontStrikeOut(true); + rule.pattern = QRegularExpression(QStringLiteral("\\~+(.*?)\\~+")); + rule.format = singleLineStrikeThroughFormat; + highlightingRules.append(rule); +//STRIKETHROUGH + +//CODE BLOCK + multiLineCodeBlockFormat.setFontFamily("Roboto Mono"); + rule.pattern = QRegularExpression(QStringLiteral("\\`\\`\\`(.*?)\\`\\`\\`")); + rule.format = multiLineCodeBlockFormat; + highlightingRules.append(rule); +//CODE BLOCK +} + +void StatusSyntaxHighlighter::highlightBlock(const QString &text) +{ + for (const HighlightingRule &rule : qAsConst(highlightingRules)) { + QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.format); + } + } + setCurrentBlockState(0); +} + +QQuickTextDocument *StatusSyntaxHighlighterHelper::quickTextDocument() const { + return m_quicktextdocument; +} + +void StatusSyntaxHighlighterHelper::setQuickTextDocument( + QQuickTextDocument *quickTextDocument) { + m_quicktextdocument = quickTextDocument; + if (m_quicktextdocument) { + new StatusSyntaxHighlighter(m_quicktextdocument->textDocument()); + } +} diff --git a/src-cpp/dotherside/StatusSyntaxHighlighter.h b/src-cpp/dotherside/StatusSyntaxHighlighter.h new file mode 100644 index 0000000000..681256ddb2 --- /dev/null +++ b/src-cpp/dotherside/StatusSyntaxHighlighter.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +class QQuickTextDocument; + +class StatusSyntaxHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + StatusSyntaxHighlighter(QTextDocument *parent = nullptr); + +protected: + void highlightBlock(const QString &text) override; + +private: + struct HighlightingRule + { + QRegularExpression pattern; + QTextCharFormat format; + }; + QVector highlightingRules; + + QTextCharFormat singlelineBoldFormat; + QTextCharFormat singleLineItalicFormat; + QTextCharFormat singlelineCodeBlockFormat; + QTextCharFormat singleLineStrikeThroughFormat; + QTextCharFormat multiLineCodeBlockFormat; +}; + +class StatusSyntaxHighlighterHelper : public QObject { + Q_OBJECT + Q_PROPERTY(QQuickTextDocument *quickTextDocument READ quickTextDocument WRITE + setQuickTextDocument NOTIFY quickTextDocumentChanged) +public: + StatusSyntaxHighlighterHelper(QObject *parent = nullptr) + : QObject(parent), m_quicktextdocument(nullptr) {} + QQuickTextDocument *quickTextDocument() const; + void setQuickTextDocument(QQuickTextDocument *quickTextDocument); +signals: + void quickTextDocumentChanged(); + +private: + QQuickTextDocument *m_quicktextdocument; +}; diff --git a/src-cpp/dotherside/StatusWindow.cpp b/src-cpp/dotherside/StatusWindow.cpp new file mode 100644 index 0000000000..f13b861d62 --- /dev/null +++ b/src-cpp/dotherside/StatusWindow.cpp @@ -0,0 +1,34 @@ +#include "StatusWindow.h" + +StatusWindow::StatusWindow(QWindow *parent) +: QQuickWindow(parent), + m_isFullScreen(false) +{ + removeTitleBar(); + + connect(this, &QQuickWindow::windowStateChanged, [&](Qt::WindowState windowState) { + if (windowState == Qt::WindowNoState) { + removeTitleBar(); + m_isFullScreen = false; + emit isFullScreenChanged(); + } else if (windowState == Qt::WindowFullScreen) { + m_isFullScreen = true; + emit isFullScreenChanged(); + showTitleBar(); + } + }); +} + +void StatusWindow::toggleFullScreen() +{ + if (m_isFullScreen) { + showNormal(); + } else { + showFullScreen(); + } +} + +bool StatusWindow::isFullScreen() const +{ + return m_isFullScreen; +} diff --git a/src-cpp/dotherside/StatusWindow.h b/src-cpp/dotherside/StatusWindow.h new file mode 100644 index 0000000000..9fce57fa83 --- /dev/null +++ b/src-cpp/dotherside/StatusWindow.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class StatusWindow: public QQuickWindow +{ + Q_OBJECT + + Q_PROPERTY(bool isFullScreen READ isFullScreen NOTIFY isFullScreenChanged) + +public: + + explicit StatusWindow(QWindow *parent = nullptr); + + Q_INVOKABLE void toggleFullScreen(); + + bool isFullScreen() const; + + Q_INVOKABLE void updatePosition() { + auto point = QPoint(screen()->geometry().center().x() - geometry().width() / 2, screen()->geometry().center().y() - geometry().height() / 2); + if (point != this->position()) { + this->setPosition(point); + } + } + +signals: + void isFullScreenChanged(); + void secondInstanceDetected(); + +private: + void removeTitleBar(); + void showTitleBar(); + void initCallbacks(); + +private: + bool m_isFullScreen; +}; + diff --git a/src-cpp/dotherside/StatusWindow_osx.mm b/src-cpp/dotherside/StatusWindow_osx.mm new file mode 100644 index 0000000000..64cf268c21 --- /dev/null +++ b/src-cpp/dotherside/StatusWindow_osx.mm @@ -0,0 +1,35 @@ +#include "StatusWindow.h" + +#include +#include +#include +#include +#include +#include +#include + +void StatusWindow::removeTitleBar() +{ + NSView *nsView = reinterpret_cast(this->winId()); + NSWindow *window = [nsView window]; + + window.titlebarAppearsTransparent = true; + window.titleVisibility = NSWindowTitleHidden; + window.styleMask |= NSWindowStyleMaskFullSizeContentView; + NSButton* close = [window standardWindowButton:NSWindowCloseButton]; + NSView* titleBarContainerView = close.superview.superview; + [titleBarContainerView setHidden:YES]; +} + +void StatusWindow::showTitleBar() +{ + NSView *nsView = reinterpret_cast(this->winId()); + NSWindow *window = [nsView window]; + + window.titlebarAppearsTransparent = true; + window.titleVisibility = NSWindowTitleHidden; + window.styleMask |= NSWindowStyleMaskFullSizeContentView; + NSButton* close = [window standardWindowButton:NSWindowCloseButton]; + NSView* titleBarContainerView = close.superview.superview; + [titleBarContainerView setHidden:NO]; +} diff --git a/src-cpp/dotherside/StatusWindow_other.cpp b/src-cpp/dotherside/StatusWindow_other.cpp new file mode 100644 index 0000000000..164b6f3db8 --- /dev/null +++ b/src-cpp/dotherside/StatusWindow_other.cpp @@ -0,0 +1,11 @@ +#include "StatusWindow.h" + +void StatusWindow::removeTitleBar() +{ + +} + +void StatusWindow::showTitleBar() +{ + +} diff --git a/src-cpp/logs.cpp b/src-cpp/logs.cpp new file mode 100644 index 0000000000..a5d1a621cb --- /dev/null +++ b/src-cpp/logs.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +void logFormatter(QtMsgType type, const QMessageLogContext& context, const QString& msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + const char* file = context.file ? context.file : ""; + QByteArray function = context.function ? (QString(QStringLiteral("\033[0;33mfunction=\033[94m") + QString(context.function)).toLocal8Bit()) + : QString("").toLocal8Bit(); + QByteArray timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz").toLocal8Bit(); + + const char* log; + + switch(type) + { + case QtDebugMsg: log = "\033[0;90mDBG \033[0m%s \033[1m%s \033[0;33mfile=\033[94m%s:%u %s\n"; break; + case QtInfoMsg: log = "\033[0;36mINF \033[0m%s \033[1m%s \033[0;33mfile=\033[94m%s:%u %s\n"; break; + case QtWarningMsg: log = "\033[0;33mWRN \033[0m%s \033[1m%s \033[0;33mfile=\033[94m%s:%u %s\n"; break; + case QtCriticalMsg: log = "\033[0;91mCRT \033[0m%s \033[1;91m%s \033[0;33mfile=\033[94m%s:%u %s\n"; break; + case QtFatalMsg: log = "\033[0;31m!!! \033[0m%s \033[1m%s \033[0;33mfile=\033[94m%s:%u %s\n"; break; + } + fprintf(stderr, log, timestamp.constData(), localMsg.constData(), file, context.line, function.constData()); +} \ No newline at end of file diff --git a/src-cpp/logs.h b/src-cpp/logs.h new file mode 100644 index 0000000000..ceff52c5d7 --- /dev/null +++ b/src-cpp/logs.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include +#include + +void logFormatter(QtMsgType type, const QMessageLogContext& context, const QString& msg); diff --git a/src-cpp/main.cpp b/src-cpp/main.cpp new file mode 100644 index 0000000000..3f42ec22b4 --- /dev/null +++ b/src-cpp/main.cpp @@ -0,0 +1,78 @@ +#include "DOtherSide.h" +#include "app_controller.h" +#include "constants.h" +#include "libstatus.h" +#include "logs.h" +#include "signals.h" +#include "singleton.h" +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + qInstallMessageHandler(logFormatter); + + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); +#endif + + QGuiApplication app(argc, argv); + app.setOrganizationName("Status"); + app.setOrganizationDomain("status.im"); + app.setApplicationName("Status Desktop"); + + if(!Constants::ensureDirectories()) return 1; + + // Init keystore + const char* initKeystoreResult = InitKeystore(Constants::applicationPath(Constants::Keystore).toUtf8().data()); + QJsonObject initKeystoreJson = QJsonDocument::fromJson(initKeystoreResult).object(); + if(initKeystoreJson["error"].toString() != "") + { + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Critical); + msgBox.setText("Could not open keystore: " + initKeystoreJson["error"].toString()); + msgBox.exec(); + return 1; + } + + QScopedPointer signalManager(Signals::Manager::instance()); + + // Registering metatypes + qRegisterMetaType("Signal"); + qRegisterMetaType("NodeSignal"); + + + qInfo("starting application controller..."); + AppController appController = AppController(); + appController.start(); + + QResource::registerResource("../resources.rcc"); + + DOtherSide::registerMetaTypes(); + + Global::Singleton::instance()->engine()->addImportPath("qrc:/./StatusQ/src"); + Global::Singleton::instance()->engine()->addImportPath("qrc:/./imports"); + + const QUrl url(QStringLiteral("qrc:/main.qml")); + QObject::connect( + Global::Singleton::instance()->engine(), + &QQmlApplicationEngine::objectCreated, + &app, + [url](QObject* obj, const QUrl& objUrl) { + if(!obj && url == objUrl) QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); + + Global::Singleton::instance()->engine()->load(url); + + qInfo("starting application..."); + return app.exec(); +}