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.

281 lines
9.5KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #pragma once
  14. //==============================================================================
  15. class LicenseQueryThread : public Thread
  16. {
  17. public:
  18. LicenseQueryThread (const String& userEmail, const String& userPassword,
  19. std::function<void (LicenseState, String)>&& cb)
  20. : Thread ("LicenseQueryThread"),
  21. email (userEmail),
  22. password (userPassword),
  23. completionCallback (std::move (cb))
  24. {
  25. startThread();
  26. }
  27. ~LicenseQueryThread() override
  28. {
  29. signalThreadShouldExit();
  30. waitForThreadToExit (6000);
  31. }
  32. void run() override
  33. {
  34. LicenseState state;
  35. auto errorMessage = runJob (std::make_unique<UserLogin> (email, password), state);
  36. if (errorMessage.isEmpty())
  37. errorMessage = runJob (std::make_unique<UserLicenseQuery> (state.authToken), state);
  38. if (errorMessage.isNotEmpty())
  39. state = {};
  40. WeakReference<LicenseQueryThread> weakThis (this);
  41. MessageManager::callAsync ([this, weakThis, state, errorMessage]
  42. {
  43. if (weakThis != nullptr)
  44. completionCallback (state, errorMessage);
  45. });
  46. }
  47. private:
  48. //==============================================================================
  49. struct AccountEnquiryBase
  50. {
  51. virtual ~AccountEnquiryBase() = default;
  52. virtual bool isPOSTLikeRequest() const = 0;
  53. virtual String getEndpointURLSuffix() const = 0;
  54. virtual StringPairArray getParameterNamesAndValues() const = 0;
  55. virtual String getExtraHeaders() const = 0;
  56. virtual int getSuccessCode() const = 0;
  57. virtual String errorCodeToString (int) const = 0;
  58. virtual bool parseServerResponse (const String&, LicenseState&) = 0;
  59. };
  60. struct UserLogin : public AccountEnquiryBase
  61. {
  62. UserLogin (const String& e, const String& p)
  63. : userEmail (e), userPassword (p)
  64. {
  65. }
  66. bool isPOSTLikeRequest() const override { return true; }
  67. String getEndpointURLSuffix() const override { return "/authenticate/projucer"; }
  68. int getSuccessCode() const override { return 200; }
  69. StringPairArray getParameterNamesAndValues() const override
  70. {
  71. StringPairArray namesAndValues;
  72. namesAndValues.set ("email", userEmail);
  73. namesAndValues.set ("password", userPassword);
  74. return namesAndValues;
  75. }
  76. String getExtraHeaders() const override
  77. {
  78. return "Content-Type: application/json";
  79. }
  80. String errorCodeToString (int errorCode) const override
  81. {
  82. switch (errorCode)
  83. {
  84. case 400: return "Please enter your email and password to sign in.";
  85. case 401: return "Your email and password are incorrect.";
  86. case 451: return "Access denied.";
  87. default: return "Something went wrong, please try again.";
  88. }
  89. }
  90. bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
  91. {
  92. auto json = JSON::parse (serverResponse);
  93. licenseState.authToken = json.getProperty ("token", {}).toString();
  94. licenseState.username = json.getProperty ("user", {}).getProperty ("username", {}).toString();
  95. auto avatarURL = json.getProperty ("user", {}).getProperty ("avatar_url", {}).toString();
  96. if (avatarURL.isNotEmpty())
  97. {
  98. URL url (avatarURL);
  99. if (auto stream = url.createInputStream (false, nullptr, nullptr, {}, 5000))
  100. {
  101. MemoryBlock mb;
  102. stream->readIntoMemoryBlock (mb);
  103. licenseState.avatar = ImageFileFormat::loadFrom (mb.getData(), mb.getSize());
  104. }
  105. }
  106. return (licenseState.authToken.isNotEmpty() && licenseState.username.isNotEmpty());
  107. }
  108. String userEmail, userPassword;
  109. };
  110. struct UserLicenseQuery : public AccountEnquiryBase
  111. {
  112. UserLicenseQuery (const String& authToken)
  113. : userAuthToken (authToken)
  114. {
  115. }
  116. bool isPOSTLikeRequest() const override { return false; }
  117. String getEndpointURLSuffix() const override { return "/user/licences/projucer"; }
  118. int getSuccessCode() const override { return 200; }
  119. StringPairArray getParameterNamesAndValues() const override
  120. {
  121. return {};
  122. }
  123. String getExtraHeaders() const override
  124. {
  125. return "x-access-token: " + userAuthToken;
  126. }
  127. String errorCodeToString (int errorCode) const override
  128. {
  129. switch (errorCode)
  130. {
  131. case 401: return "User not found or could not be verified.";
  132. default: return "User licenses info fetch failed (unknown error).";
  133. }
  134. }
  135. bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
  136. {
  137. auto json = JSON::parse (serverResponse);
  138. if (auto* licensesJson = json.getArray())
  139. {
  140. StringArray licenseTypes;
  141. for (auto& license : *licensesJson)
  142. {
  143. auto status = license.getProperty ("status", {}).toString();
  144. if (status == "active")
  145. licenseTypes.add (license.getProperty ("licence_type", {}).toString());
  146. }
  147. licenseTypes.removeEmptyStrings();
  148. licenseTypes.removeDuplicates (false);
  149. licenseState.type = [licenseTypes]()
  150. {
  151. if (licenseTypes.contains ("juce-pro")) return LicenseState::Type::pro;
  152. else if (licenseTypes.contains ("juce-indie")) return LicenseState::Type::indie;
  153. else if (licenseTypes.contains ("juce-personal")) return LicenseState::Type::personal;
  154. else if (licenseTypes.contains ("juce-edu")) return LicenseState::Type::educational;
  155. return LicenseState::Type::none;
  156. }();
  157. return (licenseState.type != LicenseState::Type::none);
  158. }
  159. return false;
  160. }
  161. String userAuthToken;
  162. };
  163. //==============================================================================
  164. static String postDataStringAsJSON (const StringPairArray& parameters)
  165. {
  166. DynamicObject::Ptr d (new DynamicObject());
  167. for (auto& key : parameters.getAllKeys())
  168. d->setProperty (key, parameters[key]);
  169. return JSON::toString (var (d.get()));
  170. }
  171. String runJob (std::unique_ptr<AccountEnquiryBase> accountEnquiryJob, LicenseState& state)
  172. {
  173. const String endpointURL = "https://api.juce.com/api/v1";
  174. auto url = URL (endpointURL + accountEnquiryJob->getEndpointURLSuffix());
  175. auto isPOST = accountEnquiryJob->isPOSTLikeRequest();
  176. if (isPOST)
  177. url = url.withPOSTData (postDataStringAsJSON (accountEnquiryJob->getParameterNamesAndValues()));
  178. if (threadShouldExit())
  179. return "Cancelled.";
  180. int statusCode = 0;
  181. auto urlStream = url.createInputStream (isPOST, nullptr, nullptr,
  182. accountEnquiryJob->getExtraHeaders(),
  183. 5000, nullptr, &statusCode);
  184. if (urlStream == nullptr)
  185. return "Failed to connect to the web server.";
  186. if (statusCode != accountEnquiryJob->getSuccessCode())
  187. return accountEnquiryJob->errorCodeToString (statusCode);
  188. if (threadShouldExit())
  189. return "Cancelled.";
  190. String response;
  191. for (;;)
  192. {
  193. char buffer [8192];
  194. auto num = urlStream->read (buffer, sizeof (buffer));
  195. if (threadShouldExit())
  196. return "Cancelled.";
  197. if (num <= 0)
  198. break;
  199. response += buffer;
  200. }
  201. if (threadShouldExit())
  202. return "Cancelled.";
  203. if (! accountEnquiryJob->parseServerResponse (response, state))
  204. return "Failed to parse server response.";
  205. return {};
  206. }
  207. //==============================================================================
  208. const String email, password;
  209. const std::function<void (LicenseState, String)> completionCallback;
  210. //==============================================================================
  211. JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread)
  212. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread)
  213. };