mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
380 lines
14 KiB
C++
380 lines
14 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE framework.
|
|
Copyright (c) Raw Material Software Limited
|
|
|
|
JUCE is an open source framework subject to commercial or open source
|
|
licensing.
|
|
|
|
By downloading, installing, or using the JUCE framework, or combining the
|
|
JUCE framework with any other source code, object code, content or any other
|
|
copyrightable work, you agree to the terms of the JUCE End User Licence
|
|
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
|
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
|
do not agree to the terms of these agreements, we will not license the JUCE
|
|
framework to you, and you must discontinue the installation or download
|
|
process and cease use of the JUCE framework.
|
|
|
|
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
|
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
|
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
|
|
|
Or:
|
|
|
|
You may also use this code under the terms of the AGPLv3:
|
|
https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
|
|
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
|
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
|
MERCHANTABILITY OR FITNESS FOR A PARTICULAR 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::agplv3:
|
|
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 final : 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 final : 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 { ThreadPoolOptions{}.withNumberOfThreads (1) };
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread)
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread)
|
|
};
|