diff --git a/libnymea-core/jsonrpc/jsonrpcserver.cpp b/libnymea-core/jsonrpc/jsonrpcserver.cpp index 6f34f682..ea339035 100644 --- a/libnymea-core/jsonrpc/jsonrpcserver.cpp +++ b/libnymea-core/jsonrpc/jsonrpcserver.cpp @@ -116,9 +116,18 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject setReturns("Version", returns); params.clear(); returns.clear(); - setDescription("SetNotificationStatus", "Enable/Disable notifications for this connections."); - params.insert("enabled", JsonTypes::basicTypeToString(JsonTypes::Bool)); + setDescription("SetNotificationStatus", "Enable/Disable notifications for this connections. Either \"enabled\" or or """ + "\"namespaces\" needs to be given but not both of them. The boolean based " + "\"enabled\" parameter will enable/disable all notifications at once. If " + "instead the list-based \"namespaces\" parameter is provided, all given namespaces" + "will be enabled, the others will be disabled. The return value of \"success\" will " + "indicate success of the operation. The \"enabled\" property in the return value is " + "deprecated and used for legacy compatibilty only. It will be set to true if at least " + "one namespace has been enabled."); + params.insert("o:enabled", JsonTypes::basicTypeToString(JsonTypes::Bool)); + params.insert("o:namespaces", QVariantList() << QStringLiteral("$ref:Namespace")); setParams("SetNotificationStatus", params); + returns.insert("namespaces", QVariantList() << QStringLiteral("$ref:Namespace")); returns.insert("enabled", JsonTypes::basicTypeToString(JsonTypes::Bool)); setReturns("SetNotificationStatus", returns); @@ -261,8 +270,18 @@ JsonReply* JsonRPCServer::Introspect(const QVariantMap ¶ms) const { Q_UNUSED(params) + // We need to add dynamic stuff ourselves + QVariantMap allTypes = JsonTypes::allTypes(); + QStringList namespaces; + foreach (const QString &namespaceString, m_handlers.keys()) { + namespaces.append(namespaceString); + } + // We need to sort them to have a predictable ordering + std::sort(namespaces.begin(), namespaces.end()); + allTypes.insert("Namespace", namespaces); + QVariantMap data; - data.insert("types", JsonTypes::allTypes()); + data.insert("types", allTypes); QVariantMap methods; foreach (JsonHandler *handler, m_handlers) methods.unite(handler->introspect(QMetaMethod::Method)); @@ -291,10 +310,27 @@ JsonReply* JsonRPCServer::Version(const QVariantMap ¶ms) const JsonReply* JsonRPCServer::SetNotificationStatus(const QVariantMap ¶ms) { QUuid clientId = this->property("clientId").toUuid(); - Q_ASSERT_X(m_clientNotifications.contains(clientId), "JsonRPCServer", "SetNotificationStatus for an unknown client called"); - m_clientNotifications[clientId] = params.value("enabled").toBool(); + Q_ASSERT_X(m_clientTransports.contains(clientId), "JsonRPCServer", "Invalid client ID."); + + QStringList enabledNamespaces; + foreach (const QString &namespaceName, m_handlers.keys()) { + if (params.contains("enabled")) { + if (params.value("enabled").toBool()) { + enabledNamespaces.append(namespaceName); + } + } else { + if (params.value("namespaces").toList().contains(namespaceName)) { + enabledNamespaces.append(namespaceName); + } + } + } + qCDebug(dcJsonRpc()) << "Notification settings for client" << clientId << ":" << enabledNamespaces; + m_clientNotifications[clientId] = enabledNamespaces; + QVariantMap returns; - returns.insert("enabled", m_clientNotifications[clientId]); + returns.insert("namespaces", m_clientNotifications[clientId]); + // legacy, deprecated + returns.insert("enabled", m_clientNotifications[clientId].count() > 0); return createReply(returns); } @@ -690,8 +726,10 @@ void JsonRPCServer::sendNotification(const QVariantMap ¶ms) qCDebug(dcJsonRpc()) << "Sending notification:" << handler->name() + "." + method.name(); qCDebug(dcJsonRpcTraffic()) << "Notification content:" << data; - foreach (const QUuid &clientId, m_clientNotifications.keys(true)) { - m_clientTransports.value(clientId)->sendData(clientId, data); + foreach (const QUuid &clientId, m_clientNotifications.keys()) { + if (m_clientNotifications.value(clientId).contains(handler->name())) { + m_clientTransports.value(clientId)->sendData(clientId, data); + } } } @@ -784,9 +822,6 @@ void JsonRPCServer::clientConnected(const QUuid &clientId) m_clientTransports.insert(clientId, interface); - // If authentication is required, notifications are disabled by default. Clients must enable them with a valid token - m_clientNotifications.insert(clientId, !interface->configuration().authenticationEnabled); - // Initialize the connection locale to the settings default m_clientLocales.insert(clientId, NymeaCore::instance()->configuration()->locale()); diff --git a/libnymea-core/jsonrpc/jsonrpcserver.h b/libnymea-core/jsonrpc/jsonrpcserver.h index 3920f487..09fc0d21 100644 --- a/libnymea-core/jsonrpc/jsonrpcserver.h +++ b/libnymea-core/jsonrpc/jsonrpcserver.h @@ -104,7 +104,7 @@ private: QHash m_clientTransports; QHash m_clientBuffers; - QHash m_clientNotifications; + QHash m_clientNotifications; QHash m_clientLocales; QHash m_pushButtonTransactions; QHash m_newConnectionWaitTimers; diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index 72de8895..717d6f48 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -2305,6 +2305,11 @@ QPair JsonTypes::validateVariant(const QVariant &templateVariant, qCWarning(dcJsonRpc()) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(mediaBrowserIconRef()); return result; } + } else if (refName == "$ref:Namespace") { + // This is quite hacky, but unless we explicitly propagate the namespace info in here we can't know. + // Let's assume jsonrpcserver handles this properly... + // Actually this entire jsontypes file should probably be split up into the handlers themselves but that's a different story. + return qMakePair(true, QString()); } else { Q_ASSERT_X(false, "JsonTypes", QString("Unhandled ref: %1").arg(refName).toLatin1().data()); return report(false, QString("Unhandled ref %1. Server implementation incomplete.").arg(refName)); diff --git a/nymea.pri b/nymea.pri index 9196a1fe..b4413341 100644 --- a/nymea.pri +++ b/nymea.pri @@ -3,7 +3,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=3 -JSON_PROTOCOL_VERSION_MINOR=0 +JSON_PROTOCOL_VERSION_MINOR=1 REST_API_VERSION=1 LIBNYMEA_API_VERSION_MAJOR=3 LIBNYMEA_API_VERSION_MINOR=0 diff --git a/tests/auto/api.json b/tests/auto/api.json index 82f9952a..ad60e14a 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -3.0 +3.1 { "methods": { "Actions.ExecuteAction": { @@ -625,12 +625,18 @@ } }, "JSONRPC.SetNotificationStatus": { - "description": "Enable/Disable notifications for this connections.", + "description": "Enable/Disable notifications for this connections. Either \"enabled\" or or \"namespaces\" needs to be given but not both of them. The boolean based \"enabled\" parameter will enable/disable all notifications at once. If instead the list-based \"namespaces\" parameter is provided, all given namespaceswill be enabled, the others will be disabled. The return value of \"success\" will indicate success of the operation. The \"enabled\" property in the return value is deprecated and used for legacy compatibilty only. It will be set to true if at least one namespace has been enabled.", "params": { - "enabled": "Bool" + "o:enabled": "Bool", + "o:namespaces": [ + "$ref:Namespace" + ] }, "returns": { - "enabled": "Bool" + "enabled": "Bool", + "namespaces": [ + "$ref:Namespace" + ] } }, "JSONRPC.SetupCloudConnection": { @@ -1655,6 +1661,19 @@ "password": "String", "username": "String" }, + "Namespace": [ + "Actions", + "Configuration", + "Devices", + "Events", + "JSONRPC", + "Logging", + "NetworkManager", + "Rules", + "States", + "System", + "Tags" + ], "NetworkDeviceState": [ "NetworkDeviceStateUnknown", "NetworkDeviceStateUnmanaged", diff --git a/tests/auto/configurations/testconfigurations.cpp b/tests/auto/configurations/testconfigurations.cpp index e7678b9b..5680e6d0 100644 --- a/tests/auto/configurations/testconfigurations.cpp +++ b/tests/auto/configurations/testconfigurations.cpp @@ -78,7 +78,7 @@ void TestConfigurations::getConfigurations() void TestConfigurations::testTimeZones() { - enableNotifications(); + enableNotifications({"Configuration"}); QVariantMap params; QVariant response; QVariantMap configurations; QVariantList configurationChangedNotifications; @@ -173,7 +173,7 @@ void TestConfigurations::testTimeZones() void TestConfigurations::testServerName() { - enableNotifications(); + enableNotifications({"Configuration"}); // Get current configurations QVariantMap basicConfigurationMap = loadBasicConfiguration(); @@ -234,7 +234,7 @@ void TestConfigurations::testServerName() void TestConfigurations::testLanguages() { - enableNotifications(); + enableNotifications({"Configuration"}); // Get current configurations QVariantMap basicConfigurationMap = loadBasicConfiguration(); @@ -287,7 +287,7 @@ void TestConfigurations::testLanguages() // Restart the server and check if the language will be loaded correctly restartServer(); - enableNotifications(); + enableNotifications({"Configuration"}); // Get configuration @@ -310,7 +310,7 @@ void TestConfigurations::testLanguages() void TestConfigurations::testDebugServerConfiguration() { - enableNotifications(); + enableNotifications({"Configuration"}); // Get current configurations QVariantMap basicConfigurationMap = loadBasicConfiguration(); diff --git a/tests/auto/jsonrpc/testjsonrpc.cpp b/tests/auto/jsonrpc/testjsonrpc.cpp index 48121703..ebbc3ac7 100644 --- a/tests/auto/jsonrpc/testjsonrpc.cpp +++ b/tests/auto/jsonrpc/testjsonrpc.cpp @@ -46,8 +46,8 @@ private slots: void introspect(); - void enableDisableNotifications_data(); - void enableDisableNotifications(); + void enableDisableNotifications_legacy_data(); + void enableDisableNotifications_legacy(); void deviceAddedRemovedNotifications(); void ruleAddedRemovedNotifications(); @@ -621,7 +621,7 @@ void TestJSONRPC::introspect() } } -void TestJSONRPC::enableDisableNotifications_data() +void TestJSONRPC::enableDisableNotifications_legacy_data() { QTest::addColumn("enabled"); @@ -629,7 +629,7 @@ void TestJSONRPC::enableDisableNotifications_data() QTest::newRow("disabled") << "false"; } -void TestJSONRPC::enableDisableNotifications() +void TestJSONRPC::enableDisableNotifications_legacy() { QFETCH(QString, enabled); @@ -638,12 +638,27 @@ void TestJSONRPC::enableDisableNotifications() QVariant response = injectAndWait("JSONRPC.SetNotificationStatus", params); QCOMPARE(response.toMap().value("params").toMap().value("enabled").toString(), enabled); + + qCDebug(dcTests()) << "Enabled notifications:" << response.toMap().value("params").toMap().value("namespaces").toList(); + + QStringList expectedNamespaces; + if (enabled == "true") { + expectedNamespaces << "Actions" << "NetworkManager" << "Devices" << "System" << "Rules" << "States" << "Logging" << "Tags" << "JSONRPC" << "Configuration" << "Events"; + } + std::sort(expectedNamespaces.begin(), expectedNamespaces.end()); + + QStringList actualNamespaces; + foreach (const QVariant &ns, response.toMap().value("params").toMap().value("namespaces").toList()) { + actualNamespaces << ns.toString(); + } + std::sort(actualNamespaces.begin(), actualNamespaces.end()); + + QCOMPARE(expectedNamespaces, actualNamespaces); } void TestJSONRPC::deviceAddedRemovedNotifications() { - // enable notificartions - QCOMPARE(enableNotifications(), true); + enableNotifications({"Devices"}); // Setup connection to mock client QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); @@ -689,8 +704,7 @@ void TestJSONRPC::deviceAddedRemovedNotifications() void TestJSONRPC::ruleAddedRemovedNotifications() { - // enable notificartions - QCOMPARE(enableNotifications(), true); + enableNotifications({"Rules"}); // Setup connection to mock client QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); @@ -934,7 +948,7 @@ void TestJSONRPC::deviceChangedNotifications() void TestJSONRPC::stateChangeEmitsNotifications() { - QCOMPARE(enableNotifications(), true); + enableNotifications({"Devices"}); bool found = false; // Setup connection to mock client diff --git a/tests/auto/logging/testlogging.cpp b/tests/auto/logging/testlogging.cpp index 033874a5..249d71f7 100644 --- a/tests/auto/logging/testlogging.cpp +++ b/tests/auto/logging/testlogging.cpp @@ -217,8 +217,7 @@ void TestLogging::eventLogs() QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test"); Device *device = devices.first(); - // enable notifications - QCOMPARE(enableNotifications(), true); + enableNotifications({"Events"}); // Setup connection to mock client QNetworkAccessManager nam; @@ -295,8 +294,7 @@ void TestLogging::actionLog() params.insert("deviceId", m_mockDeviceId); params.insert("params", actionParams); - // enable notifications - QCOMPARE(enableNotifications(), true); + enableNotifications({"Logging"}); QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); @@ -472,7 +470,7 @@ void TestLogging::deviceLogs() void TestLogging::testDoubleValues() { - QCOMPARE(enableNotifications(), true); + enableNotifications({"Logging"}); // Add display pin device which contains a double value @@ -666,7 +664,7 @@ void TestLogging::testLimits() void TestLogging::removeDevice() { // enable notifications - QCOMPARE(enableNotifications(), true); + enableNotifications({"Logging"}); // get this logentry with filter QVariantMap params; diff --git a/tests/auto/mqttbroker/testmqttbroker.cpp b/tests/auto/mqttbroker/testmqttbroker.cpp index 502df3bd..1a69f475 100644 --- a/tests/auto/mqttbroker/testmqttbroker.cpp +++ b/tests/auto/mqttbroker/testmqttbroker.cpp @@ -58,7 +58,7 @@ void TestMqttBroker::initTestCase() void TestMqttBroker::testServerConfigurationAPI() { // Set up notifications spy - enableNotifications(); + enableNotifications({"Configuration"}); QSignalSpy notificationsSpy(m_mockTcpServer, &MockTcpServer::outgoingData); // Get server instances @@ -168,7 +168,7 @@ void TestMqttBroker::testServerConfigurationAPI() void TestMqttBroker::testPolicyConfigurationAPI() { // Set up notifications spy - enableNotifications(); + enableNotifications({"Configuration"}); QSignalSpy notificationsSpy(m_mockTcpServer, &MockTcpServer::outgoingData); // Get polcies diff --git a/tests/auto/restlogging/testrestlogging.cpp b/tests/auto/restlogging/testrestlogging.cpp index ec63cfa0..54994fec 100644 --- a/tests/auto/restlogging/testrestlogging.cpp +++ b/tests/auto/restlogging/testrestlogging.cpp @@ -142,8 +142,7 @@ void TestRestLogging::eventLogs() QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test"); Device *device = devices.first(); - // enable notifications - QCOMPARE(enableNotifications(), true); + enableNotifications({"Logging"}); // Setup connection to mock client QNetworkAccessManager nam; @@ -216,7 +215,7 @@ void TestRestLogging::actionLog() params.insert("params", actionParams); // enable notifications - QCOMPARE(enableNotifications(), true); + enableNotifications({"Logging"}); QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); @@ -341,7 +340,7 @@ void TestRestLogging::actionLog() void TestRestLogging::removeDevice() { // enable notifications - QCOMPARE(enableNotifications(), true); + enableNotifications({"Logging"}); QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index d1854d34..aa351845 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -890,7 +890,7 @@ void TestRules::editRules() verifyRuleError(response); // enable notifications - QCOMPARE(enableNotifications(), true); + enableNotifications({"Rules"}); // now create the new rule and edit the original one params.clear(); @@ -1856,7 +1856,7 @@ void TestRules::testChildEvaluator_data() DeviceId testDeviceId = addDisplayPinDevice(); QVERIFY2(!testDeviceId.isNull(), "Could not add push button device for child evaluators"); - enableNotifications(); + enableNotifications({"Rules"}); // Create child evaluators // Action diff --git a/tests/auto/tags/testtags.cpp b/tests/auto/tags/testtags.cpp index 00c13a2a..a626dbe4 100644 --- a/tests/auto/tags/testtags.cpp +++ b/tests/auto/tags/testtags.cpp @@ -89,8 +89,7 @@ void TestTags::addTag() QFETCH(QString, value); QFETCH(TagsStorage::TagError, expectedError); - // enable notificartions - QCOMPARE(enableNotifications(), true); + enableNotifications({"Tags"}); // Setup connection to mock client QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); @@ -124,8 +123,7 @@ void TestTags::addTag() void TestTags::updateTagValue() { - // enable notificartions - QCOMPARE(enableNotifications(), true); + enableNotifications({"Tags"}); // Setup connection to mock client QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); @@ -180,8 +178,7 @@ void TestTags::updateTagValue() void TestTags::removeTag() { - // enable notificartions - QCOMPARE(enableNotifications(), true); + enableNotifications({"Tags"}); // Setup connection to mock client QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); diff --git a/tests/auto/timemanager/testtimemanager.cpp b/tests/auto/timemanager/testtimemanager.cpp index 6636a736..0a063610 100644 --- a/tests/auto/timemanager/testtimemanager.cpp +++ b/tests/auto/timemanager/testtimemanager.cpp @@ -2011,7 +2011,7 @@ void TestTimeManager::initTimeManager() { cleanupMockHistory(); removeAllRules(); - enableNotifications(); + enableNotifications({"Rules"}); NymeaCore::instance()->timeManager()->stopTimer(); qDebug() << NymeaCore::instance()->timeManager()->currentTime().toString(); qDebug() << NymeaCore::instance()->timeManager()->currentDate().toString(); diff --git a/tests/scripts/introspect.sh b/tests/scripts/introspect.sh index 79017391..f1b90091 100755 --- a/tests/scripts/introspect.sh +++ b/tests/scripts/introspect.sh @@ -3,5 +3,10 @@ if [ -z $1 ]; then echo "usage: $0 host" else - (echo '{"id":1, "method": "JSONRPC.Introspect"}'; sleep 1) | nc $1 2222 + +cat < &) { + connect(&nam, &QNetworkAccessManager::sslErrors, [&nam](QNetworkReply *reply, const QList &) { reply->ignoreSslErrors(); }); QSignalSpy clientSpy(&nam, SIGNAL(finished(QNetworkReply*))); @@ -363,16 +363,16 @@ void NymeaTestBase::verifyReply(QNetworkReply *reply, const QByteArray &data, co // } } -bool NymeaTestBase::enableNotifications() +void NymeaTestBase::enableNotifications(const QStringList &namespaces) { - QVariantMap notificationParams; - notificationParams.insert("enabled", true); - QVariant response = injectAndWait("JSONRPC.SetNotificationStatus", notificationParams); - if (response.toMap().value("params").toMap().value("enabled").toBool() != true) { - return false; + QVariantList variantList; + foreach (const QString &ns, namespaces) { + variantList << ns; } - qDebug() << "Notifications enabled."; - return true; + QVariantMap notificationParams; + notificationParams.insert("namespaces", variantList); + QVariant response = injectAndWait("JSONRPC.SetNotificationStatus", notificationParams); + QCOMPARE(response.toMap().value("params").toMap().value("namespaces").toList(), variantList); } bool NymeaTestBase::disableNotifications() diff --git a/tests/testlib/nymeatestbase.h b/tests/testlib/nymeatestbase.h index 79f08d68..5926462a 100644 --- a/tests/testlib/nymeatestbase.h +++ b/tests/testlib/nymeatestbase.h @@ -59,7 +59,7 @@ protected: void verifyReply(QNetworkReply *reply, const QByteArray &data, const int &expectedStatus); - bool enableNotifications(); + void enableNotifications(const QStringList &namespaces); bool disableNotifications(); inline void verifyError(const QVariant &response, const QString &fieldName, const QString &error)