The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

372 lines
14KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  19. //==============================================================================
  20. namespace LicenseHelpers
  21. {
  22. inline LicenseState::Type licenseTypeForString (const String& licenseString)
  23. {
  24. if (licenseString == "juce-pro") return LicenseState::Type::pro;
  25. if (licenseString == "juce-indie") return LicenseState::Type::indie;
  26. if (licenseString == "juce-edu") return LicenseState::Type::educational;
  27. if (licenseString == "juce-personal") return LicenseState::Type::personal;
  28. jassertfalse; // unknown type
  29. return LicenseState::Type::none;
  30. }
  31. using LicenseVersionAndType = std::pair<int, LicenseState::Type>;
  32. inline LicenseVersionAndType findBestLicense (std::vector<LicenseVersionAndType>&& licenses)
  33. {
  34. if (licenses.size() == 1)
  35. return licenses[0];
  36. auto getValueForLicenceType = [] (LicenseState::Type type)
  37. {
  38. switch (type)
  39. {
  40. case LicenseState::Type::pro: return 4;
  41. case LicenseState::Type::indie: return 3;
  42. case LicenseState::Type::educational: return 2;
  43. case LicenseState::Type::personal: return 1;
  44. case LicenseState::Type::gpl:
  45. case LicenseState::Type::none:
  46. default: return -1;
  47. }
  48. };
  49. std::sort (licenses.begin(), licenses.end(),
  50. [getValueForLicenceType] (const LicenseVersionAndType& l1, const LicenseVersionAndType& l2)
  51. {
  52. if (l1.first > l2.first)
  53. return true;
  54. if (l1.first == l2.first)
  55. return getValueForLicenceType (l1.second) > getValueForLicenceType (l2.second);
  56. return false;
  57. });
  58. auto findFirstLicense = [&licenses] (bool isPaid)
  59. {
  60. auto iter = std::find_if (licenses.begin(), licenses.end(),
  61. [isPaid] (const LicenseVersionAndType& l)
  62. {
  63. auto proOrIndie = (l.second == LicenseState::Type::pro || l.second == LicenseState::Type::indie);
  64. return isPaid ? proOrIndie : ! proOrIndie;
  65. });
  66. return iter != licenses.end() ? *iter
  67. : LicenseVersionAndType();
  68. };
  69. auto newestPaid = findFirstLicense (true);
  70. auto newestFree = findFirstLicense (false);
  71. if (newestPaid.first >= projucerMajorVersion || newestPaid.first >= newestFree.first)
  72. return newestPaid;
  73. return newestFree;
  74. }
  75. }
  76. //==============================================================================
  77. class LicenseQueryThread
  78. {
  79. public:
  80. enum class ErrorType
  81. {
  82. busy,
  83. cancelled,
  84. connectionError,
  85. webResponseError
  86. };
  87. using ErrorMessageAndType = std::pair<String, ErrorType>;
  88. using LicenseQueryCallback = std::function<void (ErrorMessageAndType, LicenseState)>;
  89. //==============================================================================
  90. LicenseQueryThread() = default;
  91. void checkLicenseValidity (const LicenseState& state, LicenseQueryCallback completionCallback)
  92. {
  93. if (jobPool.getNumJobs() > 0)
  94. {
  95. completionCallback ({ {}, ErrorType::busy }, {});
  96. return;
  97. }
  98. jobPool.addJob ([this, state, completionCallback]
  99. {
  100. auto updatedState = state;
  101. auto result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), updatedState);
  102. WeakReference<LicenseQueryThread> weakThis (this);
  103. MessageManager::callAsync ([weakThis, result, updatedState, completionCallback]
  104. {
  105. if (weakThis != nullptr)
  106. completionCallback (result, updatedState);
  107. });
  108. });
  109. }
  110. void doSignIn (const String& email, const String& password, LicenseQueryCallback completionCallback)
  111. {
  112. cancelRunningJobs();
  113. jobPool.addJob ([this, email, password, completionCallback]
  114. {
  115. LicenseState state;
  116. auto result = runTask (std::make_unique<UserLogin> (email, password), state);
  117. if (result == ErrorMessageAndType())
  118. result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), state);
  119. if (result != ErrorMessageAndType())
  120. state = {};
  121. WeakReference<LicenseQueryThread> weakThis (this);
  122. MessageManager::callAsync ([weakThis, result, state, completionCallback]
  123. {
  124. if (weakThis != nullptr)
  125. completionCallback (result, state);
  126. });
  127. });
  128. }
  129. void cancelRunningJobs()
  130. {
  131. jobPool.removeAllJobs (true, 500);
  132. }
  133. private:
  134. //==============================================================================
  135. struct AccountEnquiryBase
  136. {
  137. virtual ~AccountEnquiryBase() = default;
  138. virtual bool isPOSTLikeRequest() const = 0;
  139. virtual String getEndpointURLSuffix() const = 0;
  140. virtual StringPairArray getParameterNamesAndValues() const = 0;
  141. virtual String getExtraHeaders() const = 0;
  142. virtual int getSuccessCode() const = 0;
  143. virtual String errorCodeToString (int) const = 0;
  144. virtual bool parseServerResponse (const String&, LicenseState&) = 0;
  145. };
  146. struct UserLogin : public AccountEnquiryBase
  147. {
  148. UserLogin (const String& e, const String& p)
  149. : userEmail (e), userPassword (p)
  150. {
  151. }
  152. bool isPOSTLikeRequest() const override { return true; }
  153. String getEndpointURLSuffix() const override { return "/authenticate/projucer"; }
  154. int getSuccessCode() const override { return 200; }
  155. StringPairArray getParameterNamesAndValues() const override
  156. {
  157. StringPairArray namesAndValues;
  158. namesAndValues.set ("email", userEmail);
  159. namesAndValues.set ("password", userPassword);
  160. return namesAndValues;
  161. }
  162. String getExtraHeaders() const override
  163. {
  164. return "Content-Type: application/json";
  165. }
  166. String errorCodeToString (int errorCode) const override
  167. {
  168. switch (errorCode)
  169. {
  170. case 400: return "Please enter your email and password to sign in.";
  171. case 401: return "Your email and password are incorrect.";
  172. case 451: return "Access denied.";
  173. default: return "Something went wrong, please try again.";
  174. }
  175. }
  176. bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
  177. {
  178. auto json = JSON::parse (serverResponse);
  179. licenseState.authToken = json.getProperty ("token", {}).toString();
  180. licenseState.username = json.getProperty ("user", {}).getProperty ("username", {}).toString();
  181. return (licenseState.authToken.isNotEmpty() && licenseState.username.isNotEmpty());
  182. }
  183. String userEmail, userPassword;
  184. };
  185. struct UserLicenseQuery : public AccountEnquiryBase
  186. {
  187. UserLicenseQuery (const String& authToken)
  188. : userAuthToken (authToken)
  189. {
  190. }
  191. bool isPOSTLikeRequest() const override { return false; }
  192. String getEndpointURLSuffix() const override { return "/user/licences/projucer"; }
  193. int getSuccessCode() const override { return 200; }
  194. StringPairArray getParameterNamesAndValues() const override
  195. {
  196. return {};
  197. }
  198. String getExtraHeaders() const override
  199. {
  200. return "x-access-token: " + userAuthToken;
  201. }
  202. String errorCodeToString (int errorCode) const override
  203. {
  204. switch (errorCode)
  205. {
  206. case 401: return "User not found or could not be verified.";
  207. default: return "User licenses info fetch failed (unknown error).";
  208. }
  209. }
  210. bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
  211. {
  212. auto json = JSON::parse (serverResponse);
  213. if (auto* licensesJson = json.getArray())
  214. {
  215. std::vector<LicenseHelpers::LicenseVersionAndType> licenses;
  216. for (auto& license : *licensesJson)
  217. {
  218. auto version = license.getProperty ("product_version", {}).toString().trim();
  219. auto type = license.getProperty ("licence_type", {}).toString();
  220. auto status = license.getProperty ("status", {}).toString();
  221. if (status == "active" && type.isNotEmpty() && version.isNotEmpty())
  222. licenses.push_back ({ version.getIntValue(), LicenseHelpers::licenseTypeForString (type) });
  223. }
  224. if (! licenses.empty())
  225. {
  226. auto bestLicense = LicenseHelpers::findBestLicense (std::move (licenses));
  227. licenseState.version = bestLicense.first;
  228. licenseState.type = bestLicense.second;
  229. }
  230. return true;
  231. }
  232. return false;
  233. }
  234. String userAuthToken;
  235. };
  236. //==============================================================================
  237. static String postDataStringAsJSON (const StringPairArray& parameters)
  238. {
  239. DynamicObject::Ptr d (new DynamicObject());
  240. for (auto& key : parameters.getAllKeys())
  241. d->setProperty (key, parameters[key]);
  242. return JSON::toString (var (d.get()));
  243. }
  244. static ErrorMessageAndType runTask (std::unique_ptr<AccountEnquiryBase> accountEnquiryTask, LicenseState& state)
  245. {
  246. const ErrorMessageAndType cancelledError ("Cancelled.", ErrorType::cancelled);
  247. const String endpointURL ("https://api.juce.com/api/v1");
  248. URL url (endpointURL + accountEnquiryTask->getEndpointURLSuffix());
  249. auto isPOST = accountEnquiryTask->isPOSTLikeRequest();
  250. if (isPOST)
  251. url = url.withPOSTData (postDataStringAsJSON (accountEnquiryTask->getParameterNamesAndValues()));
  252. if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
  253. return cancelledError;
  254. int statusCode = 0;
  255. auto urlStream = url.createInputStream (URL::InputStreamOptions (isPOST ? URL::ParameterHandling::inPostData
  256. : URL::ParameterHandling::inAddress)
  257. .withExtraHeaders (accountEnquiryTask->getExtraHeaders())
  258. .withConnectionTimeoutMs (5000)
  259. .withStatusCode (&statusCode));
  260. if (urlStream == nullptr)
  261. return { "Failed to connect to the web server.", ErrorType::connectionError };
  262. if (statusCode != accountEnquiryTask->getSuccessCode())
  263. return { accountEnquiryTask->errorCodeToString (statusCode), ErrorType::webResponseError };
  264. if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
  265. return cancelledError;
  266. String response;
  267. for (;;)
  268. {
  269. char buffer [8192] = "";
  270. auto num = urlStream->read (buffer, sizeof (buffer));
  271. if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
  272. return cancelledError;
  273. if (num <= 0)
  274. break;
  275. response += buffer;
  276. }
  277. if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
  278. return cancelledError;
  279. if (! accountEnquiryTask->parseServerResponse (response, state))
  280. return { "Failed to parse server response.", ErrorType::webResponseError };
  281. return {};
  282. }
  283. //==============================================================================
  284. ThreadPool jobPool { 1 };
  285. //==============================================================================
  286. JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread)
  287. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread)
  288. };