#include "AccessKeyFetcher.h" #include // for strrchr #include // for initializer_list #include // for operator!=, operator== #include // for remove_extent_t #include // for vector #include "BellLogger.h" // for AbstractLogger #include "CSpotContext.h" // for Context #include "Logger.h" // for CSPOT_LOG #include "MercurySession.h" // for MercurySession, MercurySession::Res... #include "Packet.h" // for cspot #include "TimeProvider.h" // for TimeProvider #include "Utils.h" // for string_format #include "WrappedSemaphore.h" #ifdef BELL_ONLY_CJSON #include "cJSON.h" #else #include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json #include "nlohmann/json_fwd.hpp" // for json #endif using namespace cspot; static std::string CLIENT_ID = "65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id static std::string SCOPES = "streaming,user-library-read,user-library-modify,user-top-read,user-read-" "recently-played"; // Required access scopes AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr ctx) : ctx(ctx) { this->updateSemaphore = std::make_shared(); } bool AccessKeyFetcher::isExpired() { if (accessKey.empty()) { return true; } if (ctx->timeProvider->getSyncedTimestamp() > expiresAt) { return true; } return false; } std::string AccessKeyFetcher::getAccessKey() { if (!isExpired()) { return accessKey; } updateAccessKey(); return accessKey; } void AccessKeyFetcher::updateAccessKey() { if (keyPending) { // Already pending refresh request return; } keyPending = true; CSPOT_LOG(info, "Access token expired, fetching new one..."); std::string url = string_format("hm://keymaster/token/authenticated?client_id=%s&scope=%s", CLIENT_ID.c_str(), SCOPES.c_str()); auto timeProvider = this->ctx->timeProvider; ctx->session->execute( MercurySession::RequestType::GET, url, [this, timeProvider](MercurySession::Response& res) { if (res.fail) return; auto accessJSON = std::string((char*)res.parts[0].data(), res.parts[0].size()); #ifdef BELL_ONLY_CJSON cJSON* jsonBody = cJSON_Parse(accessJSON.c_str()); this->accessKey = cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring; int expiresIn = cJSON_GetObjectItem(jsonBody, "expiresIn")->valueint; cJSON_Delete(jsonBody); #else auto jsonBody = nlohmann::json::parse(accessJSON); this->accessKey = jsonBody["accessToken"]; int expiresIn = jsonBody["expiresIn"]; #endif expiresIn = expiresIn / 2; // Refresh token before it expires this->expiresAt = timeProvider->getSyncedTimestamp() + (expiresIn * 1000); updateSemaphore->give(); }); updateSemaphore->twait(5000); // Mark as not pending for refresh keyPending = false; }