|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2017 - ROLI Ltd.
-
- 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 5 End-User License
- Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
- 27th April 2017).
-
- End User License Agreement: www.juce.com/juce-5-licence
- Privacy Policy: www.juce.com/juce-5-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
-
-
- //==============================================================================
- struct NetWorkerThread : public Thread,
- private AsyncUpdater
- {
- NetWorkerThread() : Thread ("License") {}
-
- ~NetWorkerThread() override
- {
- JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
-
- signalThreadShouldExit();
- cancelPendingUpdate();
- finished.signal();
-
- {
- ScopedLock lock (weakReferenceLock);
-
- if (currentInputStream != nullptr)
- currentInputStream->cancel();
- }
-
- waitForThreadToExit (-1);
- }
-
- //==============================================================================
- void executeOnMessageThreadAndBlock (std::function<void()> f, bool signalWhenFinished = true)
- {
- // only call this on the worker thread
- jassert (Thread::getCurrentThreadId() == getThreadId());
-
- if (! isWaiting)
- {
- ScopedValueSetter<bool> reentrant (isWaiting, true);
-
- finished.reset();
-
- if (! threadShouldExit())
- {
- functionToExecute = [signalWhenFinished, f, this] () { f(); if (signalWhenFinished) finished.signal(); };
- triggerAsyncUpdate();
- finished.wait (-1);
- }
- }
- else
- {
- // only one task at a time
- jassertfalse;
- return;
- }
- }
-
- WebInputStream* getSharedWebInputStream (const URL& url, const bool usePost)
- {
- ScopedLock lock (weakReferenceLock);
-
- if (threadShouldExit())
- return nullptr;
-
- jassert (currentInputStream == nullptr);
- return (currentInputStream = new WeakWebInputStream (*this, url, usePost));
- }
-
- bool isWaiting = false;
- WaitableEvent finished;
-
- private:
- //==============================================================================
- void handleAsyncUpdate() override
- {
- if (functionToExecute)
- {
- std::function<void()> f;
- std::swap (f, functionToExecute);
-
- if (! threadShouldExit())
- f();
- }
- }
-
- //==============================================================================
- struct WeakWebInputStream : public WebInputStream
- {
- WeakWebInputStream (NetWorkerThread& workerThread, const URL& url, const bool usePost)
- : WebInputStream (url, usePost), owner (workerThread) {}
-
- ~WeakWebInputStream()
- {
- ScopedLock lock (owner.weakReferenceLock);
- owner.currentInputStream = nullptr;
- }
-
- NetWorkerThread& owner;
- WeakReference<WeakWebInputStream>::Master masterReference;
- friend class WeakReference<WeakWebInputStream>;
- };
-
- //==============================================================================
- friend struct WeakWebInputStream;
-
- std::function<void()> functionToExecute;
- CriticalSection weakReferenceLock;
- WebInputStream* currentInputStream = nullptr;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NetWorkerThread)
- };
-
- //==============================================================================
- //==============================================================================
- //==============================================================================
- struct LicenseThread : NetWorkerThread
- {
- LicenseThread (LicenseController& licenseController, bool shouldSelectNewLicense)
- : owner (licenseController), selectNewLicense (shouldSelectNewLicense)
- {
- startThread();
- }
-
- String getAuthToken()
- {
- if (owner.state.authToken.isNotEmpty())
- return owner.state.authToken;
-
- selectNewLicense = false;
- HashMap<String, String> result;
-
- if (! queryWebview ("https://auth.roli.com/signin/projucer?redirect=projucer://receive-auth-token?token=",
- "receive-auth-token", result))
- return {};
-
- return result["token"];
- }
-
- // returns true if any information was updated
- void updateUserInfo (LicenseState& stateToUpdate)
- {
- jassert (stateToUpdate.authToken.isNotEmpty());
-
- auto accessTokenHeader = "x-access-token: " + stateToUpdate.authToken;
- std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL ("https://api.roli.com/api/v1/user"), false));
-
- if (shared != nullptr)
- {
- const int statusCode = shared->withExtraHeaders (accessTokenHeader).getStatusCode();
-
- if (statusCode == 200)
- {
- var result = JSON::parse (shared->readEntireStreamAsString());
- shared.reset();
-
- auto newState = licenseStateFromJSON (result, stateToUpdate.authToken, stateToUpdate.avatar);
-
- if (newState.type != LicenseState::Type::notLoggedIn)
- stateToUpdate = newState;
- }
- else if (statusCode == 401)
- {
- selectNewLicense = false;
-
- // un-authorised: token has expired
- stateToUpdate = LicenseState();
- }
- }
- }
-
- void updateLicenseType (LicenseState& stateToUpdate)
- {
- bool requiredWebview = false;
- String licenseChooserPage = "https://juce.com/webviews/select_license";
-
- jassert (stateToUpdate.authToken.isNotEmpty());
- jassert (stateToUpdate.type != LicenseState::Type::notLoggedIn);
-
- auto accessTokenHeader = "x-access-token: " + stateToUpdate.authToken;
- StringArray licenses;
-
- while ((licenses.isEmpty() || selectNewLicense) && ! threadShouldExit())
- {
- static Identifier licenseTypeIdentifier ("type");
- static Identifier licenseStatusIdentifier ("status");
- static Identifier projucerLicenseTypeIdentifier ("licence_type");
- static Identifier productNameIdentifier ("product_name");
- static Identifier licenseIdentifier ("licence");
- static Identifier serialIdentifier ("serial_number");
- static Identifier versionIdentifier ("product_version");
- static Identifier searchInternalIdentifier ("search_internal_id");
-
- if (! selectNewLicense)
- {
- std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL ("https://api.roli.com/api/v1/user/licences?search_internal_id=com.roli.projucer&version=5"),
- false));
- if (shared == nullptr)
- break;
-
- var json = JSON::parse (shared->withExtraHeaders (accessTokenHeader)
- .readEntireStreamAsString());
- shared.reset();
-
- if (auto* jsonLicenses = json.getArray())
- {
- for (auto& v : *jsonLicenses)
- {
- if (auto* obj = v.getDynamicObject())
- {
- const String& productType = obj->getProperty (projucerLicenseTypeIdentifier);
- const String& status = obj->getProperty (licenseStatusIdentifier);
-
- if (productType.isNotEmpty() && (status.isEmpty() || status == "active"))
- licenses.add (productType);
- }
- }
- }
- else
- {
- // no internet -> then use the last valid license
- if (stateToUpdate.type != LicenseState::Type::notLoggedIn
- && stateToUpdate.type != LicenseState::Type::noLicenseChosenYet)
- return;
- }
-
- if (! licenses.isEmpty())
- break;
- }
-
- // ask the user to select a license
- HashMap<String, String> result;
- requiredWebview = true;
-
- if (! queryWebview (licenseChooserPage, {}, result))
- break;
-
- const String& redirectURL = result["page-redirect"];
- const String& productKey = result["register-product"];
- const String& chosenLicenseType = result["redeem-licence-type"];
-
- if (redirectURL.isNotEmpty())
- {
- licenseChooserPage = "https://juce.com/webviews/register-product";
- continue;
- }
-
- if (productKey.isNotEmpty())
- {
- DynamicObject::Ptr redeemObject (new DynamicObject());
- redeemObject->setProperty (serialIdentifier, productKey);
-
- String postData (JSON::toString (var (redeemObject.get())));
-
- std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL ("https://api.roli.com/api/v1/user/products").withPOSTData (postData),
- true));
- if (shared == nullptr)
- break;
-
- int statusCode = shared->withExtraHeaders (accessTokenHeader)
- .withExtraHeaders ("Content-Type: application/json")
- .getStatusCode();
-
- licenseChooserPage = String ("https://juce.com/webviews/register-product?error=")
- + String (statusCode == 404 ? "invalid" : "server");
-
- if (statusCode == 200)
- selectNewLicense = false;
-
- continue;
- }
-
- if (chosenLicenseType.isNotEmpty())
- {
- // redeem the license
- DynamicObject::Ptr jsonLicenseObject (new DynamicObject());
- jsonLicenseObject->setProperty (projucerLicenseTypeIdentifier, chosenLicenseType);
- jsonLicenseObject->setProperty (versionIdentifier, 5);
-
-
- DynamicObject::Ptr jsonLicenseRequest (new DynamicObject());
- jsonLicenseRequest->setProperty (licenseIdentifier, var (jsonLicenseObject.get()));
- jsonLicenseRequest->setProperty (searchInternalIdentifier, "com.roli.projucer");
- jsonLicenseRequest->setProperty (licenseTypeIdentifier, "software");
-
- String postData (JSON::toString (var (jsonLicenseRequest.get())));
- std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL ("https://api.roli.com/api/v1/user/products/redeem")
- .withPOSTData (postData),
- true));
-
- if (shared != nullptr)
- {
- int statusCode = shared->withExtraHeaders (accessTokenHeader)
- .withExtraHeaders ("Content-Type: application/json")
- .getStatusCode();
-
- if (statusCode == 200)
- selectNewLicense = false;
-
- continue;
- }
- }
-
- break;
- }
-
- HashMap<String, String> result;
-
- if (requiredWebview && ! threadShouldExit())
- queryWebview ("https://juce.com/webviews/registration-complete", "licence_provisioned", result);
-
- stateToUpdate.type = getBestLicenseTypeFromLicenses (licenses);
- }
-
- //==============================================================================
- void run() override
- {
- LicenseState workState (owner.state);
-
- while (! threadShouldExit())
- {
- workState.authToken = getAuthToken();
-
- if (workState.authToken.isEmpty())
- return;
-
- // read the user information
- updateUserInfo (workState);
-
- if (threadShouldExit())
- return;
-
- updateIfChanged (workState);
-
- // if the last step logged us out then retry
- if (workState.authToken.isEmpty())
- continue;
-
- // check if the license has changed
- updateLicenseType (workState);
-
- if (threadShouldExit())
- return;
-
- updateIfChanged (workState);
- closeWebviewOnMessageThread (0);
- finished.wait (60 * 5 * 1000);
- }
- }
-
- //==============================================================================
- LicenseState licenseStateFromJSON (const var& json, const String& authToken, const Image& fallbackAvatar)
- {
- static Identifier usernameIdentifier ("username");
- static Identifier emailIdentifier ("email");
- static Identifier avatarURLIdentifier ("avatar_url");
-
- LicenseState result;
-
- if (auto* obj = json.getDynamicObject())
- {
- result.type = LicenseState::Type::noLicenseChosenYet;
- result.username = obj->getProperty (usernameIdentifier);
- result.authToken = authToken;
- result.email = obj->getProperty (emailIdentifier);
- result.avatar = fallbackAvatar;
-
- String avatarURL = obj->getProperty (avatarURLIdentifier);
-
- if (avatarURL.isNotEmpty())
- {
- std::unique_ptr<WebInputStream> shared (getSharedWebInputStream (URL (avatarURL), false));
-
- if (shared != nullptr)
- {
- MemoryBlock mb;
- shared->readIntoMemoryBlock (mb);
-
- result.avatar = ImageFileFormat::loadFrom (mb.getData(), mb.getSize());
- }
- }
- }
-
- return result;
- }
-
- //==============================================================================
- bool queryWebview (const String& startURL, const String& valueToQuery, HashMap<String, String>& result)
- {
- executeOnMessageThreadAndBlock ([&] () { owner.queryWebview (startURL, valueToQuery, result); }, false);
- return (! threadShouldExit());
- }
-
- void closeWebviewOnMessageThread (int result)
- {
- executeOnMessageThreadAndBlock ([this, result] () { owner.closeWebview (result); });
- }
-
- static bool stringArrayContainsSubstring (const StringArray& stringArray, const String& substring)
- {
- jassert (substring.isNotEmpty());
-
- for (auto element : stringArray)
- if (element.containsIgnoreCase (substring))
- return true;
-
- return false;
- }
-
- static LicenseState::Type getBestLicenseTypeFromLicenses (const StringArray& licenses)
- {
- if (stringArrayContainsSubstring (licenses, "juce-pro")) return LicenseState::Type::pro;
- else if (stringArrayContainsSubstring (licenses, "juce-indie")) return LicenseState::Type::indie;
- else if (stringArrayContainsSubstring (licenses, "juce-personal")) return LicenseState::Type::personal;
- else if (stringArrayContainsSubstring (licenses, "juce-edu")) return LicenseState::Type::edu;
-
- return LicenseState::Type::noLicenseChosenYet;
- }
-
- void updateIfChanged (const LicenseState& newState)
- {
- LicenseState updatedState (owner.state);
- bool changed = false;
- bool shouldUpdateLicenseType = (newState.type != LicenseState::Type::noLicenseChosenYet
- || updatedState.type == LicenseState::Type::notLoggedIn);
-
- if (newState.type != LicenseState::Type::notLoggedIn) updatedState.avatar = newState.avatar;
-
- if (owner.state.type != newState.type && shouldUpdateLicenseType) { updatedState.type = newState.type; changed = true; }
- if (owner.state.authToken != newState.authToken) { updatedState.authToken = newState.authToken; changed = true; }
- if (owner.state.username != newState.username) { updatedState.username = newState.username; changed = true; }
- if (owner.state.email != newState.email) { updatedState.email = newState.email; changed = true; }
- if (owner.state.avatar.isValid() != newState.avatar.isValid()) { changed = true; }
-
- if (changed)
- executeOnMessageThreadAndBlock ([this, updatedState] { owner.updateState (updatedState); });
- }
-
- //==============================================================================
- LicenseController& owner;
- bool selectNewLicense;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseThread)
- };
|