From cd17134c1967214276183bc3b51c7b524b16ce91 Mon Sep 17 00:00:00 2001 From: nymea Date: Fri, 6 Sep 2019 00:26:10 +0200 Subject: [PATCH] moved authentication into sonos class --- .gitmodules | 3 - sonos/devicepluginsonos.cpp | 140 +++++++--------------- sonos/devicepluginsonos.h | 9 +- sonos/devicepluginsonos.json | 8 ++ sonos/oauth.cpp | 221 ----------------------------------- sonos/oauth.h | 71 ----------- sonos/sonos.cpp | 202 +++++++++++++++++++++++++------- sonos/sonos.h | 41 ++++--- sonos/sonos.pro | 2 - 9 files changed, 243 insertions(+), 454 deletions(-) delete mode 100644 sonos/oauth.cpp delete mode 100644 sonos/oauth.h diff --git a/.gitmodules b/.gitmodules index 7581b25b..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "sonos/sonos/noson"] - path = sonos/sonos/noson - url = https://github.com/janbar/noson.git diff --git a/sonos/devicepluginsonos.cpp b/sonos/devicepluginsonos.cpp index 5441b669..02bb5be6 100644 --- a/sonos/devicepluginsonos.cpp +++ b/sonos/devicepluginsonos.cpp @@ -32,7 +32,6 @@ DevicePluginSonos::DevicePluginSonos() { - } @@ -83,20 +82,22 @@ Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device) }); } - if(!m_tokenRefreshTimer) { - m_tokenRefreshTimer = new QTimer(this); - m_tokenRefreshTimer->setSingleShot(false); - connect(m_tokenRefreshTimer, &QTimer::timeout, this, &DevicePluginSonos::onRefreshTimeout); - } - if (device->deviceClassId() == sonosConnectionDeviceClassId) { qCDebug(dcSonos()) << "Sonos OAuth setup complete"; + Sonos *sonos; + if (m_setupSonosConnections.keys().contains(device->id())) { + //Fresh device setup, has already a fresh access token + sonos = m_setupSonosConnections.take(device->id()); + } else { + //device loaded from the device database, needs a new access token; + pluginStorage()->beginGroup(device->id().toString()); + QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); + pluginStorage()->endGroup(); - pluginStorage()->beginGroup(device->id().toString()); - QByteArray accessToken = pluginStorage()->value("access_token").toByteArray(); - pluginStorage()->endGroup(); + sonos = new Sonos(hardwareManager()->networkManager(), "b15cbf8c-a39c-47aa-bd93-635a96e9696c", "c086ba71-e562-430b-a52f-867c6482fd11", "", this); + sonos->getAccessTokenFromRefreshToken(refreshToken); + } - Sonos *sonos = new Sonos(hardwareManager()->networkManager(), accessToken, this); connect(sonos, &Sonos::connectionChanged, this, &DevicePluginSonos::onConnectionChanged); connect(sonos, &Sonos::householdIdsReceived, this, &DevicePluginSonos::onHouseholdIdsReceived); connect(sonos, &Sonos::groupsReceived, this, &DevicePluginSonos::onGroupsReceived); @@ -115,22 +116,13 @@ Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device) DevicePairingInfo DevicePluginSonos::pairDevice(DevicePairingInfo &devicePairingInfo) { if (devicePairingInfo.deviceClassId() == sonosConnectionDeviceClassId) { - QString clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c"; - QString clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11"; - - QUrl url("https://api.sonos.com/login/v3/oauth"); - QUrlQuery queryParams; - queryParams.addQueryItem("client_id", clientId); - queryParams.addQueryItem("redirect_uri", "https://127.0.0.1:8888"); - queryParams.addQueryItem("response_type", "code"); - queryParams.addQueryItem("scope", "playback-control-all"); - queryParams.addQueryItem("state", QUuid::createUuid().toString()); - url.setQuery(queryParams); + Sonos *sonos = new Sonos(hardwareManager()->networkManager(), "b15cbf8c-a39c-47aa-bd93-635a96e9696c", "c086ba71-e562-430b-a52f-867c6482fd11", "", this); + QUrl url = sonos->getLoginUrl(QUrl("https://127.0.0.1:8000")); qCDebug(dcSonos()) << "Sonos url:" << url; - devicePairingInfo.setOAuthUrl(url); devicePairingInfo.setStatus(Device::DeviceErrorNoError); + m_setupSonosConnections.insert(devicePairingInfo.deviceId(), sonos); return devicePairingInfo; } @@ -148,61 +140,39 @@ DevicePairingInfo DevicePluginSonos::confirmPairing(DevicePairingInfo &devicePai qCDebug(dcSonos()) << "Secret is" << secret; QUrl url(secret); QUrlQuery query(url); - qCDebug(dcSonos()) << "Acess code is:" << query.queryItemValue("code"); + QByteArray accessCode = query.queryItemValue("code").toLocal8Bit(); + qCDebug(dcSonos()) << "Acess code is:" << accessCode; - QString accessCode = query.queryItemValue("code"); + Sonos *sonos = m_setupSonosConnections.value(devicePairingInfo.deviceId()); - // Obtaining access token - url = QUrl("https://api.sonos.com/login/v3/oauth/access"); - query.clear(); - query.addQueryItem("grant_type", "authorization_code"); - query.addQueryItem("code", accessCode); - query.addQueryItem("redirect_uri", "https%3A%2F%2F127.0.0.1%3A8888"); - url.setQuery(query); - - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded;charset=utf-8"); - - QByteArray clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c"; - QByteArray clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11"; - - QByteArray auth = QByteArray(clientId + ':' + clientSecret).toBase64(QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals); - request.setRawHeader("Authorization", QString("Basic %1").arg(QString(auth)).toUtf8()); - - QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray()); - connect(reply, &QNetworkReply::finished, this, [this, reply, devicePairingInfo](){ - reply->deleteLater(); + if (!sonos) { + m_setupSonosConnections.remove(devicePairingInfo.deviceId()); + sonos->deleteLater(); + devicePairingInfo.setStatus(Device::DeviceErrorHardwareFailure); + return devicePairingInfo; + } + sonos->getAccessTokenFromAuthorizationCode(accessCode); + connect(sonos, &Sonos::authenticationStatusChanged, this, [devicePairingInfo, this](bool authenticated){ + Sonos *sonos = static_cast(sender()); DevicePairingInfo info(devicePairingInfo); - - QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); - qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson(); - if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) { + if(!authenticated) { + m_setupSonosConnections.remove(info.deviceId()); + sonos->deleteLater(); info.setStatus(Device::DeviceErrorSetupFailed); emit pairingFinished(info); return; } - qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString(); - QByteArray accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); - - qCDebug(dcSonos()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString(); - QByteArray refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray(); + QByteArray accessToken = sonos->accessToken(); + QByteArray refreshToken = sonos->refreshToken(); pluginStorage()->beginGroup(info.deviceId().toString()); pluginStorage()->setValue("access_token", accessToken); pluginStorage()->setValue("refresh_token", refreshToken); pluginStorage()->endGroup(); - if (jsonDoc.toVariant().toMap().contains("expires_in")) { - int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); - qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); - //m_tokenRefreshTimer->start((expireTime - 20) * 1000); - //TODO - } - info.setStatus(Device::DeviceErrorNoError); emit pairingFinished(info); }); - devicePairingInfo.setStatus(Device::DeviceErrorAsync); return devicePairingInfo; } @@ -348,39 +318,18 @@ void DevicePluginSonos::onConnectionChanged(bool connected) } } -void DevicePluginSonos::onRefreshTimeout() +void DevicePluginSonos::onAuthenticationStatusChanged(bool authenticated) { - qCDebug(dcSonos) << "Refresh authentication token"; - - QUrlQuery query; - query.addQueryItem("grant_type", "refresh_token"); - query.addQueryItem("refresh_token", m_sonosConnectionRefreshToken); - - QUrl url("https://api.sonos.com/login/v3/oauth"); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8"); - QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray()); - connect(reply, &QNetworkReply::finished, this, [this, reply](){ - reply->deleteLater(); - - QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); - qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson(); - if(!jsonDoc.toVariant().toMap().contains("access_token")) { - return; - } - qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString(); - m_sonosConnectionAccessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); - - if (jsonDoc.toVariant().toMap().contains("expires_in")) { - int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); - qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); - if (!m_tokenRefreshTimer) { - qWarning(dcSonos()) << "Token refresh timer not initialized"; - return; - } - m_tokenRefreshTimer->start((expireTime - 20) * 1000); - } - }); + Sonos *sonosConnection = static_cast(sender()); + Device *device = m_sonosConnections.key(sonosConnection); + device->setStateValue(sonosConnectionLoggedInStateTypeId, authenticated); + if (!authenticated) { + //refresh access token needs to be refreshed + pluginStorage()->beginGroup(device->id().toString()); + QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); + pluginStorage()->endGroup(); + sonosConnection->getAccessTokenFromRefreshToken(refreshToken); + } } void DevicePluginSonos::onHouseholdIdsReceived(QList householdIds) @@ -419,8 +368,9 @@ void DevicePluginSonos::onPlaylistSummaryReceived(const QString &householdId, So } } -void DevicePluginSonos::onGroupsReceived(QList groupObjects) +void DevicePluginSonos::onGroupsReceived(const QString &householdId, QList groupObjects) { + Q_UNUSED(householdId); Sonos *sonos = static_cast(sender()); Device *parentDevice = m_sonosConnections.key(sonos); if (!parentDevice) diff --git a/sonos/devicepluginsonos.h b/sonos/devicepluginsonos.h index 7d0cd69e..fcf2eb38 100644 --- a/sonos/devicepluginsonos.h +++ b/sonos/devicepluginsonos.h @@ -29,8 +29,6 @@ #include #include -#include - class DevicePluginSonos : public DevicePlugin { @@ -54,8 +52,8 @@ public: private: PluginTimer *m_pluginTimer5sec = nullptr; PluginTimer *m_pluginTimer60sec = nullptr; - QTimer *m_tokenRefreshTimer = nullptr; + QHash m_setupSonosConnections; QHash m_sonosConnections; QList m_householdIds; @@ -63,16 +61,17 @@ private: QByteArray m_sonosConnectionRefreshToken; QHash m_pendingActions; + private slots: void onConnectionChanged(bool connected); - void onRefreshTimeout(); + void onAuthenticationStatusChanged(bool authenticated); void onHouseholdIdsReceived(QList householdIds); void onFavouritesReceived(const QString &householdId, QList favourites); void onPlaylistsReceived(const QString &householdId, QList playlists); void onPlaylistSummaryReceived(const QString &householdId, Sonos::PlaylistSummaryObject playlistSummary); - void onGroupsReceived(QList groupIds); + void onGroupsReceived(const QString &householdId, QList groupIds); void onPlayBackStatusReceived(const QString &groupId, Sonos::PlayBackObject playBack); void onMetadataStatusReceived(const QString &groupId, Sonos::MetadataStatus metaDataStatus); void onVolumeReceived(const QString &groupId, Sonos::VolumeObject groupVolume); diff --git a/sonos/devicepluginsonos.json b/sonos/devicepluginsonos.json index af19f4d0..cfbc056d 100644 --- a/sonos/devicepluginsonos.json +++ b/sonos/devicepluginsonos.json @@ -26,6 +26,14 @@ "displayNameEvent": "connected changed", "defaultValue": false, "type": "bool" + }, + { + "id": "48b5c1bf-7df0-45d0-9ba3-290fc3acddc3", + "name": "loggedIn", + "displayName": "Logged in", + "displayNameEvent": "Logged in changed", + "defaultValue": false, + "type": "bool" } ] }, diff --git a/sonos/oauth.cpp b/sonos/oauth.cpp deleted file mode 100644 index 2c34c2b9..00000000 --- a/sonos/oauth.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "oauth.h" -#include "extern-plugininfo.h" - -#include -#include -#include - -OAuth::OAuth(QString clientId, QObject *parent) : - QObject(parent), - m_clientId(clientId), - m_authenticated(false) -{ - m_networkManager = new QNetworkAccessManager(this); - connect(m_networkManager, &QNetworkAccessManager::finished, this, &OAuth::replyFinished); - - m_timer = new QTimer(this); - m_timer->setSingleShot(false); - - connect(m_timer, &QTimer::timeout, this, &OAuth::refreshTimeout); -} - -QUrl OAuth::url() const -{ - return m_url; -} - -void OAuth::setUrl(const QUrl &url) -{ - m_url = url; -} - -QUrlQuery OAuth::query() const -{ - return m_query; -} - -void OAuth::setQuery(const QUrlQuery &query) -{ - m_query = query; -} - -QString OAuth::clientId() const -{ - return m_clientId; -} - -void OAuth::setClientId(const QString &clientId) -{ - m_clientId = clientId; -} - -QString OAuth::scope() const -{ - return m_scope; -} - -void OAuth::setScope(const QString &scope) -{ - m_scope = scope; -} - -QString OAuth::authorizationCode() const -{ - return m_token; -} - -QString OAuth::bearerToken() const -{ - return m_token; -} - -bool OAuth::authenticated() const -{ - return m_authenticated; -} - -void OAuth::startAuthentication() -{ - qCDebug(dcSonos) << "Start authentication"; - - QUrlQuery query; - query.addQueryItem("client_id", m_clientId); - query.addQueryItem("redirect_uri", m_redirectUri); - query.addQueryItem("response_type", "code"); - query.addQueryItem("scope", m_scope); - m_state = QUuid().toByteArray(); - query.addQueryItem("state", m_state); - setQuery(query); - - QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8"); - m_tokenRequests.append(m_networkManager->post(request, m_query.toString().toUtf8())); -} - -void OAuth::setAuthenticated(const bool &authenticated) -{ - if (authenticated) { - qCDebug(dcSonos()) << "Authenticated successfully"; - } else { - m_timer->stop(); - qCWarning(dcSonos) << "Authentication failed"; - } - m_authenticated = authenticated; - emit authenticationChanged(); -} - -void OAuth::setToken(const QString &token) -{ - m_token = token; - emit tokenChanged(); -} - -void OAuth::replyFinished(QNetworkReply *reply) -{ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - reply->deleteLater(); - // token request - if (m_tokenRequests.contains(reply)) { - - QByteArray data = reply->readAll(); - m_tokenRequests.removeAll(reply); - - // check HTTP status code - if (status != 200) { - qCWarning(dcSonos) << "Request token reply HTTP error:" << status << reply->errorString(); - qCWarning(dcSonos) << data; - setAuthenticated(false); - return; - } - - // check JSON - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(dcSonos) << "Request token reply JSON error:" << error.errorString(); - setAuthenticated(false); - return; - } - - if (!jsonDoc.toVariant().toMap().contains("code")) { - qCWarning(dcSonos) << "Could not get code" << jsonDoc.toJson(); - setAuthenticated(false); - return; - } - - if (!jsonDoc.toVariant().toMap().contains("state")) { - qCWarning(dcSonos) << "Could not get state" << jsonDoc.toJson(); - return; - } - - if (jsonDoc.toVariant().toMap().value("state").toString() != m_state) { - qCWarning(dcSonos) << "State doesn't match. Expected:" << m_state << "Received:" << jsonDoc.toVariant().toMap().value("state").toString(); - } - - setToken(jsonDoc.toVariant().toMap().value("code").toString()); - setAuthenticated(true); - - if (jsonDoc.toVariant().toMap().contains("expires_in") && jsonDoc.toVariant().toMap().contains("refresh_token")) { - int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); - m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toString(); - qCDebug(dcSonos) << "Token will be refreshed in" << expireTime << "[s]"; - m_timer->start((expireTime - 20) * 1000); - } - - } else if (m_refreshTokenRequests.contains(reply)) { - - QByteArray data = reply->readAll(); - m_refreshTokenRequests.removeAll(reply); - - // check HTTP status code - if (status != 200) { - qCWarning(dcSonos) << "Refresh token reply HTTP error:" << status << reply->errorString(); - qCWarning(dcSonos) << data; - setAuthenticated(false); - return; - } - - // check JSON - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(dcSonos) << "Refresh token reply JSON error:" << error.errorString(); - setAuthenticated(false); - return; - } - - if (!jsonDoc.toVariant().toMap().contains("access_token")) { - qCWarning(dcSonos) << "Could not get access token after refresh" << jsonDoc.toJson(); - setAuthenticated(false); - return; - } - - - setToken(jsonDoc.toVariant().toMap().value("access_token").toString()); - qCDebug(dcSonos) << "Token refreshed successfully"; - - if (jsonDoc.toVariant().toMap().contains("expires_in") && jsonDoc.toVariant().toMap().contains("refresh_token")) { - int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); - m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toString(); - qCDebug(dcSonos) << "Token will be refreshed in" << expireTime << "[s]"; - m_timer->start((expireTime - 20) * 1000); - } - - if (!authenticated()) - setAuthenticated(true); - } -} - -void OAuth::refreshTimeout() -{ - qCDebug(dcSonos) << "Refresh authentication token for"; - - QUrlQuery query; - query.addQueryItem("grant_type", "refresh_token"); - query.addQueryItem("refresh_token", m_refreshToken); - query.addQueryItem("client_id", m_clientId); - - QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8"); - m_refreshTokenRequests.append(m_networkManager->post(request, query.toString().toUtf8())); -} diff --git a/sonos/oauth.h b/sonos/oauth.h deleted file mode 100644 index 205eda51..00000000 --- a/sonos/oauth.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef OAUTH_H -#define OAUTH_H - - -#include -#include -#include -#include -#include -#include -#include - -class OAuth : public QObject -{ - Q_OBJECT - -public: - explicit OAuth(QString clientId, QObject *parent = nullptr); - - QUrl url() const; - void setUrl(const QUrl &url); - - QUrlQuery query() const; - void setQuery(const QUrlQuery &query); - - QString clientId() const; - void setClientId(const QString &clientId); - - QString scope() const; - void setScope(const QString &scope); - - QByteArray authorizationCode() const; - QByteArray bearerToken() const; - - bool authenticated() const; - - void startAuthentication(); - -private: - - QNetworkAccessManager *m_networkManager; - QTimer *m_timer; - QList m_tokenRequests; - QList m_refreshTokenRequests; - - QUrl m_url; - QUrlQuery m_query; - QString m_clientId; - QString m_scope; - QString m_state; - QString m_redirectUri; - QString m_responseType; - - QString m_token; - QString m_refreshToken; - - bool m_authenticated; - - void setAuthenticated(const bool &authenticated); - void setToken(const QString &token); - -private slots: - void replyFinished(QNetworkReply *reply); - void refreshTimeout(); - -signals: - void authenticationChanged(); - void tokenChanged(); -}; - -#endif // OAUTH_H diff --git a/sonos/sonos.cpp b/sonos/sonos.cpp index 9544e078..6fc27dba 100644 --- a/sonos/sonos.cpp +++ b/sonos/sonos.cpp @@ -26,17 +26,46 @@ #include #include #include +#include -Sonos::Sonos(NetworkAccessManager *networkmanager, const QByteArray &accessToken, QObject *parent) : +Sonos::Sonos(NetworkAccessManager *networkmanager, const QByteArray &clientKey, const QByteArray &clientSecret, const QByteArray &refreshToken, QObject *parent) : QObject(parent), - m_accessToken(accessToken), + m_clientKey(clientKey), + m_clientSecret(clientSecret), + m_refreshToken(refreshToken), m_networkManager(networkmanager) { + if(!m_tokenRefreshTimer) { + m_tokenRefreshTimer = new QTimer(this); + m_tokenRefreshTimer->setSingleShot(true); + connect(m_tokenRefreshTimer, &QTimer::timeout, this, &Sonos::onRefreshTimeout); + } } -void Sonos::setAccessToken(const QByteArray &accessToken) +QUrl Sonos::getLoginUrl(const QUrl &redirectUrl) { - m_accessToken = accessToken; + QString clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c"; + + QUrl url("https://api.sonos.com/login/v3/oauth"); + QUrlQuery queryParams; + queryParams.addQueryItem("client_id", clientId); + queryParams.addQueryItem("redirect_uri", redirectUrl.toString()); + queryParams.addQueryItem("response_type", "code"); + queryParams.addQueryItem("scope", "playback-control-all"); + queryParams.addQueryItem("state", QUuid::createUuid().toString()); + url.setQuery(queryParams); + + return url; +} + +QByteArray Sonos::accessToken() +{ + return m_accessToken; +} + +QByteArray Sonos::refreshToken() +{ + return m_refreshToken; } void Sonos::getHouseholds() @@ -44,7 +73,7 @@ void Sonos::getHouseholds() QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/households")); QNetworkReply *reply = m_networkManager->get(request); qDebug(dcSonos()) << "Sending request" << request.url() << request.rawHeaderList() << request.rawHeader("Authorization"); @@ -53,6 +82,12 @@ void Sonos::getHouseholds() int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); connectionChanged(true); + + if (status == 401) { + //Authentication required + getAccessTokenFromRefreshToken(m_refreshToken); + return; + } // Check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcSonos()) << "Request error:" << status << reply->errorString(); @@ -82,7 +117,7 @@ QUuid Sonos::loadFavorite(const QString &groupId, const QString &favouriteId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/favourites")); QUuid actionId = QUuid::createUuid(); @@ -90,7 +125,7 @@ QUuid Sonos::loadFavorite(const QString &groupId, const QString &favouriteId) object.insert("favoriteId", QJsonValue::fromVariant(favouriteId)); QJsonDocument doc(object); - QNetworkReply *reply = m_networkManager->post(request, doc.toBinaryData()); + QNetworkReply *reply = m_networkManager->post(request, doc.toJson(QJsonDocument::Compact)); connect(reply, &QNetworkReply::finished, this, [reply, actionId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -101,8 +136,6 @@ QUuid Sonos::loadFavorite(const QString &groupId, const QString &favouriteId) emit actionExecuted(actionId, false); return; } - - //TODO parse response emit actionExecuted(actionId, true); }); return actionId; @@ -113,7 +146,7 @@ void Sonos::getFavorites(const QString &householdId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/favorites")); QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] { @@ -126,7 +159,6 @@ void Sonos::getFavorites(const QString &householdId) return; } - //qDebug(dcSonos()) << "Received response from Sonos" << reply->readAll(); QJsonDocument data = QJsonDocument::fromJson(reply->readAll()); if (!data.isObject()) return; @@ -156,10 +188,10 @@ void Sonos::getGroups(const QString &householdId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/groups")); QNetworkReply *reply = m_networkManager->get(request); - connect(reply, &QNetworkReply::finished, this, [reply, this] { + connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -187,7 +219,7 @@ void Sonos::getGroups(const QString &householdId) group.displayName = obj["name"].toString(); groupObjects.append(group); } - emit groupsReceived(groupObjects); + emit groupsReceived(householdId, groupObjects); }); } @@ -196,7 +228,7 @@ void Sonos::getGroupVolume(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume")); QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, groupId, this] { @@ -229,7 +261,7 @@ QUuid Sonos::setGroupVolume(const QString &groupId, int volume) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume")); QUuid actionId = QUuid::createUuid(); @@ -260,7 +292,7 @@ QUuid Sonos::setGroupMute(const QString &groupId, bool mute) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume/mute")); QUuid actionId = QUuid::createUuid(); @@ -292,7 +324,7 @@ QUuid Sonos::setGroupRelativeVolume(const QString &groupId, int volumeDelta) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/groupVolume/relative")); QUuid actionId = QUuid::createUuid(); @@ -324,7 +356,7 @@ void Sonos::getGroupPlaybackStatus(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback")); QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, this, groupId] { @@ -378,7 +410,7 @@ QUuid Sonos::groupLoadLineIn(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/lineIn")); QUuid actionId = QUuid::createUuid(); @@ -404,7 +436,7 @@ QUuid Sonos::groupPlay(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/play")); QUuid actionId = QUuid::createUuid(); @@ -432,7 +464,7 @@ QUuid Sonos::groupPause(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/pause")); QUuid actionId = QUuid::createUuid(); @@ -461,7 +493,7 @@ QUuid Sonos::groupSeek(const QString &groupId, int possitionMillis) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/seek")); QUuid actionId = QUuid::createUuid(); @@ -490,7 +522,7 @@ QUuid Sonos::groupSeekRelative(const QString &groupId, int deltaMillis) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/seekRelative")); QUuid actionId = QUuid::createUuid(); @@ -519,7 +551,7 @@ QUuid Sonos::groupSetPlayModes(const QString &groupId, PlayMode playMode) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/playMode")); QUuid actionId = QUuid::createUuid(); @@ -554,7 +586,7 @@ QUuid Sonos::groupSetShuffle(const QString &groupId, bool shuffle) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/playMode")); QUuid actionId = QUuid::createUuid(); @@ -586,7 +618,7 @@ QUuid Sonos::groupSetRepeat(const QString &groupId, RepeatMode repeatMode) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/playMode")); QUuid actionId = QUuid::createUuid(); @@ -630,7 +662,7 @@ QUuid Sonos::groupSetCrossfade(const QString &groupId, bool crossfade) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/playMode")); QUuid actionId = QUuid::createUuid(); @@ -662,7 +694,7 @@ QUuid Sonos::groupSkipToNextTrack(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/skipToNextTrack")); QUuid actionId = QUuid::createUuid(); @@ -688,7 +720,7 @@ QUuid Sonos::groupSkipToPreviousTrack(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/skipToPreviousTrack")); QUuid actionId = QUuid::createUuid(); @@ -714,7 +746,7 @@ QUuid Sonos::groupTogglePlayPause(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playback/togglePlayPause")); QUuid actionId = QUuid::createUuid(); @@ -740,7 +772,7 @@ void Sonos::getGroupMetadataStatus(const QString &groupId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playbackMetadata")); QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, groupId, this] { @@ -871,7 +903,7 @@ void Sonos::getPlayerVolume(const QByteArray &playerId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume")); QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, playerId, this] { @@ -902,7 +934,7 @@ QUuid Sonos::setPlayerVolume(const QByteArray &playerId, int volume) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume")); QUuid actionId = QUuid::createUuid(); @@ -934,7 +966,7 @@ QUuid Sonos::setPlayerRelativeVolume(const QByteArray &playerId, int volumeDelta QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume/relative")); QUuid actionId = QUuid::createUuid(); @@ -964,7 +996,7 @@ QUuid Sonos::setPlayerMute(const QByteArray &playerId, bool mute) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/playerVolume")); QUuid actionId = QUuid::createUuid(); @@ -994,7 +1026,7 @@ void Sonos::getPlaylist(const QString &householdId, const QString &playlistId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/playlists/getPlaylist")); @@ -1041,7 +1073,7 @@ void Sonos::getPlaylists(const QString &householdId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/households/" + householdId + "/playlists")); QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, householdId, this] { @@ -1083,7 +1115,7 @@ QUuid Sonos::loadPlaylist(const QString &groupId, const QString &playlistId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/groups/" + groupId + "/playlists")); QUuid actionId = QUuid::createUuid(); @@ -1112,7 +1144,7 @@ void Sonos::getPlayerSettings(const QString &playerId) QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/settings/player")); QNetworkReply *reply = m_networkManager->get(request); connect(reply, &QNetworkReply::finished, this, [reply, playerId, this] { @@ -1142,7 +1174,7 @@ QUuid Sonos::setPlayerSettings(const QString &playerId, PlayerSettingsObject set QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", "Bearer " + m_accessToken); - request.setRawHeader("X-Sonos-Api-Key", m_apiKey); + request.setRawHeader("X-Sonos-Api-Key", m_clientKey); request.setUrl(QUrl(m_baseControlUrl + "/players/" + playerId + "/settings/player")); QUuid actionId = QUuid::createUuid(); @@ -1169,3 +1201,91 @@ QUuid Sonos::setPlayerSettings(const QString &playerId, PlayerSettingsObject set }); return actionId; } + +void Sonos::onRefreshTimeout() +{ + qCDebug(dcSonos) << "Refresh authentication token"; + getAccessTokenFromRefreshToken(m_refreshToken); +} + + +void Sonos::getAccessTokenFromRefreshToken(const QByteArray &refreshToken) +{ + QUrlQuery query; + query.addQueryItem("grant_type", "refresh_token"); + query.addQueryItem("refresh_token", refreshToken); + + QUrl url("https://api.sonos.com/login/v3/oauth"); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8"); + QNetworkReply *reply = m_networkManager->post(request, QByteArray()); + connect(reply, &QNetworkReply::finished, this, [this, reply](){ + reply->deleteLater(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); + qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson(); + if(!jsonDoc.toVariant().toMap().contains("access_token")) { + emit authenticationStatusChanged(false); + return; + } + qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString(); + m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); + + if (jsonDoc.toVariant().toMap().contains("expires_in")) { + int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); + qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcSonos()) << "Token refresh timer not initialized"; + return; + } + m_tokenRefreshTimer->start((expireTime - 20) * 1000); + } + emit authenticationStatusChanged(true);; + }); +} + +void Sonos::getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode) +{ + // Obtaining access token + QUrl url = QUrl(m_baseAuthorizationUrl); + QUrlQuery query; + query.clear(); + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", authorizationCode); + query.addQueryItem("redirect_uri", "https%3A%2F%2F127.0.0.1%3A8888"); + url.setQuery(query); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded;charset=utf-8"); + + QByteArray auth = QByteArray(m_clientKey + ':' + m_clientSecret).toBase64(QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals); + request.setRawHeader("Authorization", QString("Basic %1").arg(QString(auth)).toUtf8()); + + QNetworkReply *reply = m_networkManager->post(request, QByteArray()); + connect(reply, &QNetworkReply::finished, this, [this, reply](){ + reply->deleteLater(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); + qCDebug(dcSonos()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson(); + if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) { + emit authenticationStatusChanged(false);; + return; + } + qCDebug(dcSonos()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString(); + m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); + + qCDebug(dcSonos()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString(); + m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray(); + + if (jsonDoc.toVariant().toMap().contains("expires_in")) { + int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); + qCDebug(dcSonos()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcSonos()) << "Token refresh timer not initialized"; + return; + } + m_tokenRefreshTimer->start((expireTime - 20) * 1000); + } + emit authenticationStatusChanged(true);; + }); +} diff --git a/sonos/sonos.h b/sonos/sonos.h index 4ce327a5..906784f3 100644 --- a/sonos/sonos.h +++ b/sonos/sonos.h @@ -24,6 +24,7 @@ #define SONOS_H #include +#include #include "network/networkaccessmanager.h" #include "devices/device.h" @@ -150,14 +151,14 @@ public: }; struct PlayBackObject { - QString itemId; - bool isDucking; - PlayBackState playbackState; - PlayMode playMode; - uint positionMillis; - QString previousItemId; - uint previousPositionMillis; - QString queueVersion; + QString itemId; + bool isDucking; + PlayBackState playbackState; + PlayMode playMode; + uint positionMillis; + QString previousItemId; + uint previousPositionMillis; + QString queueVersion; }; /* @@ -206,9 +207,13 @@ public: QList tracks; }; - explicit Sonos(NetworkAccessManager *networkManager, const QByteArray &accessToken, QObject *parent = nullptr); + explicit Sonos(NetworkAccessManager *networkManager, const QByteArray &clientId, const QByteArray &clientSecret, const QByteArray &refreshToken = "", QObject *parent = nullptr); - void setAccessToken(const QByteArray &accessToken); + QUrl getLoginUrl(const QUrl &redirectUrl); + QByteArray accessToken(); + QByteArray refreshToken(); + void getAccessTokenFromRefreshToken(const QByteArray &refreshToken); + void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode); void getHouseholds(); void getFavorites(const QString &householdId); @@ -259,21 +264,27 @@ public: QUuid setPlayerSettings(const QString &playerId, PlayerSettingsObject settings); private: - QByteArray m_baseAuthorizationUrl = "https://api.sonos.com/login/v3/oauth"; + QByteArray m_baseAuthorizationUrl = "https://api.sonos.com/login/v3/oauth/access"; QByteArray m_baseControlUrl = "https://api.ws.sonos.com/control/api/v1"; - QByteArray m_apiKey = "b15cbf8c-a39c-47aa-bd93-635a96e9696c"; + QByteArray m_clientKey; + QByteArray m_clientSecret; + QByteArray m_accessToken; + QByteArray m_refreshToken; NetworkAccessManager *m_networkManager = nullptr; + QTimer *m_tokenRefreshTimer = nullptr; private slots: - + void onRefreshTimeout(); signals: void connectionChanged(bool connected); + void authenticationStatusChanged(bool authenticated); + void householdIdsReceived(QList householdIds); void favouritesReceived(const QString &householdId, QList favourites); void playlistsReceived(const QString &householdId, QList playlists); - void groupsReceived(QList groups); + void groupsReceived(const QString &householdId, QList groups); void playlistSummaryReceived(const QString &householdId, PlaylistSummaryObject playlistSummary); void playBackStatusReceived(const QString &groupId, PlayBackObject playBack); @@ -283,7 +294,5 @@ signals: void playerVolumeReceived(const QString &playerId, VolumeObject playerVolume); void playerSettingsRecieved(const QString &playerId, PlayerSettingsObject playerSettings); void actionExecuted(QUuid actionId,bool success); - }; - #endif // SONOS_H diff --git a/sonos/sonos.pro b/sonos/sonos.pro index a107f02e..ee83ccea 100644 --- a/sonos/sonos.pro +++ b/sonos/sonos.pro @@ -11,5 +11,3 @@ SOURCES += \ HEADERS += \ devicepluginsonos.h \ sonos.h \ - -