|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 7 End-User License
- Agreement and JUCE Privacy Policy.
-
- End User License Agreement: www.juce.com/juce-7-licence
- Privacy Policy: www.juce.com/juce-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- #pragma once
-
- //==============================================================================
- namespace LicenseHelpers
- {
- inline LicenseState::Type licenseTypeForString (const String& licenseString)
- {
- if (licenseString == "juce-pro") return LicenseState::Type::pro;
- if (licenseString == "juce-indie") return LicenseState::Type::indie;
- if (licenseString == "juce-edu") return LicenseState::Type::educational;
- if (licenseString == "juce-personal") return LicenseState::Type::personal;
-
- jassertfalse; // unknown type
- return LicenseState::Type::none;
- }
-
- using LicenseVersionAndType = std::pair<int, LicenseState::Type>;
-
- inline LicenseVersionAndType findBestLicense (std::vector<LicenseVersionAndType>&& licenses)
- {
- if (licenses.size() == 1)
- return licenses[0];
-
- auto getValueForLicenceType = [] (LicenseState::Type type)
- {
- switch (type)
- {
- case LicenseState::Type::pro: return 4;
- case LicenseState::Type::indie: return 3;
- case LicenseState::Type::educational: return 2;
- case LicenseState::Type::personal: return 1;
- case LicenseState::Type::gpl:
- case LicenseState::Type::none:
- default: return -1;
- }
- };
-
- std::sort (licenses.begin(), licenses.end(),
- [getValueForLicenceType] (const LicenseVersionAndType& l1, const LicenseVersionAndType& l2)
- {
- if (l1.first > l2.first)
- return true;
-
- if (l1.first == l2.first)
- return getValueForLicenceType (l1.second) > getValueForLicenceType (l2.second);
-
- return false;
- });
-
- auto findFirstLicense = [&licenses] (bool isPaid)
- {
- auto iter = std::find_if (licenses.begin(), licenses.end(),
- [isPaid] (const LicenseVersionAndType& l)
- {
- auto proOrIndie = (l.second == LicenseState::Type::pro || l.second == LicenseState::Type::indie);
- return isPaid ? proOrIndie : ! proOrIndie;
- });
-
- return iter != licenses.end() ? *iter
- : LicenseVersionAndType();
- };
-
- auto newestPaid = findFirstLicense (true);
- auto newestFree = findFirstLicense (false);
-
- if (newestPaid.first >= projucerMajorVersion || newestPaid.first >= newestFree.first)
- return newestPaid;
-
- return newestFree;
- }
- }
-
- //==============================================================================
- class LicenseQueryThread
- {
- public:
- enum class ErrorType
- {
- busy,
- cancelled,
- connectionError,
- webResponseError
- };
-
- using ErrorMessageAndType = std::pair<String, ErrorType>;
- using LicenseQueryCallback = std::function<void (ErrorMessageAndType, LicenseState)>;
-
- //==============================================================================
- LicenseQueryThread() = default;
-
- void checkLicenseValidity (const LicenseState& state, LicenseQueryCallback completionCallback)
- {
- if (jobPool.getNumJobs() > 0)
- {
- completionCallback ({ {}, ErrorType::busy }, {});
- return;
- }
-
- jobPool.addJob ([this, state, completionCallback]
- {
- auto updatedState = state;
-
- auto result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), updatedState);
-
- WeakReference<LicenseQueryThread> weakThis (this);
- MessageManager::callAsync ([weakThis, result, updatedState, completionCallback]
- {
- if (weakThis != nullptr)
- completionCallback (result, updatedState);
- });
- });
- }
-
- void doSignIn (const String& email, const String& password, LicenseQueryCallback completionCallback)
- {
- cancelRunningJobs();
-
- jobPool.addJob ([this, email, password, completionCallback]
- {
- LicenseState state;
-
- auto result = runTask (std::make_unique<UserLogin> (email, password), state);
-
- if (result == ErrorMessageAndType())
- result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), state);
-
- if (result != ErrorMessageAndType())
- state = {};
-
- WeakReference<LicenseQueryThread> weakThis (this);
- MessageManager::callAsync ([weakThis, result, state, completionCallback]
- {
- if (weakThis != nullptr)
- completionCallback (result, state);
- });
- });
- }
-
- void cancelRunningJobs()
- {
- jobPool.removeAllJobs (true, 500);
- }
-
- private:
- //==============================================================================
- struct AccountEnquiryBase
- {
- virtual ~AccountEnquiryBase() = default;
-
- virtual bool isPOSTLikeRequest() const = 0;
- virtual String getEndpointURLSuffix() const = 0;
- virtual StringPairArray getParameterNamesAndValues() const = 0;
- virtual String getExtraHeaders() const = 0;
- virtual int getSuccessCode() const = 0;
- virtual String errorCodeToString (int) const = 0;
- virtual bool parseServerResponse (const String&, LicenseState&) = 0;
- };
-
- struct UserLogin : public AccountEnquiryBase
- {
- UserLogin (const String& e, const String& p)
- : userEmail (e), userPassword (p)
- {
- }
-
- bool isPOSTLikeRequest() const override { return true; }
- String getEndpointURLSuffix() const override { return "/authenticate/projucer"; }
- int getSuccessCode() const override { return 200; }
-
- StringPairArray getParameterNamesAndValues() const override
- {
- StringPairArray namesAndValues;
- namesAndValues.set ("email", userEmail);
- namesAndValues.set ("password", userPassword);
-
- return namesAndValues;
- }
-
- String getExtraHeaders() const override
- {
- return "Content-Type: application/json";
- }
-
- String errorCodeToString (int errorCode) const override
- {
- switch (errorCode)
- {
- case 400: return "Please enter your email and password to sign in.";
- case 401: return "Your email and password are incorrect.";
- case 451: return "Access denied.";
- default: return "Something went wrong, please try again.";
- }
- }
-
- bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
- {
- auto json = JSON::parse (serverResponse);
-
- licenseState.authToken = json.getProperty ("token", {}).toString();
- licenseState.username = json.getProperty ("user", {}).getProperty ("username", {}).toString();
-
- return (licenseState.authToken.isNotEmpty() && licenseState.username.isNotEmpty());
- }
-
- String userEmail, userPassword;
- };
-
- struct UserLicenseQuery : public AccountEnquiryBase
- {
- UserLicenseQuery (const String& authToken)
- : userAuthToken (authToken)
- {
- }
-
- bool isPOSTLikeRequest() const override { return false; }
- String getEndpointURLSuffix() const override { return "/user/licences/projucer"; }
- int getSuccessCode() const override { return 200; }
-
- StringPairArray getParameterNamesAndValues() const override
- {
- return {};
- }
-
- String getExtraHeaders() const override
- {
- return "x-access-token: " + userAuthToken;
- }
-
- String errorCodeToString (int errorCode) const override
- {
- switch (errorCode)
- {
- case 401: return "User not found or could not be verified.";
- default: return "User licenses info fetch failed (unknown error).";
- }
- }
-
- bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
- {
- auto json = JSON::parse (serverResponse);
-
- if (auto* licensesJson = json.getArray())
- {
- std::vector<LicenseHelpers::LicenseVersionAndType> licenses;
-
- for (auto& license : *licensesJson)
- {
- auto version = license.getProperty ("product_version", {}).toString().trim();
- auto type = license.getProperty ("licence_type", {}).toString();
- auto status = license.getProperty ("status", {}).toString();
-
- if (status == "active" && type.isNotEmpty() && version.isNotEmpty())
- licenses.push_back ({ version.getIntValue(), LicenseHelpers::licenseTypeForString (type) });
- }
-
- if (! licenses.empty())
- {
- auto bestLicense = LicenseHelpers::findBestLicense (std::move (licenses));
-
- licenseState.version = bestLicense.first;
- licenseState.type = bestLicense.second;
- }
-
- return true;
- }
-
- return false;
- }
-
- String userAuthToken;
- };
-
- //==============================================================================
- static String postDataStringAsJSON (const StringPairArray& parameters)
- {
- DynamicObject::Ptr d (new DynamicObject());
-
- for (auto& key : parameters.getAllKeys())
- d->setProperty (key, parameters[key]);
-
- return JSON::toString (var (d.get()));
- }
-
- static ErrorMessageAndType runTask (std::unique_ptr<AccountEnquiryBase> accountEnquiryTask, LicenseState& state)
- {
- const ErrorMessageAndType cancelledError ("Cancelled.", ErrorType::cancelled);
- const String endpointURL ("https://api.juce.com/api/v1");
-
- URL url (endpointURL + accountEnquiryTask->getEndpointURLSuffix());
-
- auto isPOST = accountEnquiryTask->isPOSTLikeRequest();
-
- if (isPOST)
- url = url.withPOSTData (postDataStringAsJSON (accountEnquiryTask->getParameterNamesAndValues()));
-
- if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
- return cancelledError;
-
- int statusCode = 0;
- auto urlStream = url.createInputStream (URL::InputStreamOptions (isPOST ? URL::ParameterHandling::inPostData
- : URL::ParameterHandling::inAddress)
- .withExtraHeaders (accountEnquiryTask->getExtraHeaders())
- .withConnectionTimeoutMs (5000)
- .withStatusCode (&statusCode));
-
- if (urlStream == nullptr)
- return { "Failed to connect to the web server.", ErrorType::connectionError };
-
- if (statusCode != accountEnquiryTask->getSuccessCode())
- return { accountEnquiryTask->errorCodeToString (statusCode), ErrorType::webResponseError };
-
- if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
- return cancelledError;
-
- String response;
-
- for (;;)
- {
- char buffer [8192] = "";
- auto num = urlStream->read (buffer, sizeof (buffer));
-
- if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
- return cancelledError;
-
- if (num <= 0)
- break;
-
- response += buffer;
- }
-
- if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
- return cancelledError;
-
- if (! accountEnquiryTask->parseServerResponse (response, state))
- return { "Failed to parse server response.", ErrorType::webResponseError };
-
- return {};
- }
-
- //==============================================================================
- ThreadPool jobPool { 1 };
-
- //==============================================================================
- JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread)
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread)
- };
|