migrating to the latest JUCE version
This commit is contained in:
@ -1,135 +1,135 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
JUCE_IMPLEMENT_SINGLETON (InAppPurchases)
|
||||
|
||||
InAppPurchases::InAppPurchases()
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
: pimpl (new Pimpl (*this))
|
||||
#endif
|
||||
{}
|
||||
|
||||
InAppPurchases::~InAppPurchases() { clearSingletonInstance(); }
|
||||
|
||||
bool InAppPurchases::isInAppPurchasesSupported() const
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
return pimpl->isInAppPurchasesSupported();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::getProductsInformation (const StringArray& productIdentifiers)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->getProductsInformation (productIdentifiers);
|
||||
#else
|
||||
Array<Product> products;
|
||||
for (auto productId : productIdentifiers)
|
||||
products.add (Product { productId, {}, {}, {}, {} });
|
||||
|
||||
listeners.call ([&] (Listener& l) { l.productsInfoReturned (products); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::purchaseProduct (const String& productIdentifier,
|
||||
const String& upgradeProductIdentifier,
|
||||
bool creditForUnusedSubscription)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->purchaseProduct (productIdentifier, upgradeProductIdentifier, creditForUnusedSubscription);
|
||||
#else
|
||||
Listener::PurchaseInfo purchaseInfo { Purchase { "", productIdentifier, {}, {}, {} }, {} };
|
||||
|
||||
listeners.call ([&] (Listener& l) { l.productPurchaseFinished (purchaseInfo, false, "In-app purchases unavailable"); });
|
||||
ignoreUnused (upgradeProductIdentifier, creditForUnusedSubscription);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::restoreProductsBoughtList (bool includeDownloadInfo, const String& subscriptionsSharedSecret)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->restoreProductsBoughtList (includeDownloadInfo, subscriptionsSharedSecret);
|
||||
#else
|
||||
listeners.call ([] (Listener& l) { l.purchasesListRestored ({}, false, "In-app purchases unavailable"); });
|
||||
ignoreUnused (includeDownloadInfo, subscriptionsSharedSecret);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::consumePurchase (const String& productIdentifier, const String& purchaseToken)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->consumePurchase (productIdentifier, purchaseToken);
|
||||
#else
|
||||
listeners.call ([&] (Listener& l) { l.productConsumed (productIdentifier, false, "In-app purchases unavailable"); });
|
||||
ignoreUnused (purchaseToken);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::addListener (Listener* l) { listeners.add (l); }
|
||||
void InAppPurchases::removeListener (Listener* l) { listeners.remove (l); }
|
||||
|
||||
void InAppPurchases::startDownloads (const Array<Download*>& downloads)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->startDownloads (downloads);
|
||||
#else
|
||||
ignoreUnused (downloads);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::pauseDownloads (const Array<Download*>& downloads)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->pauseDownloads (downloads);
|
||||
#else
|
||||
ignoreUnused (downloads);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::resumeDownloads (const Array<Download*>& downloads)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->resumeDownloads (downloads);
|
||||
#else
|
||||
ignoreUnused (downloads);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::cancelDownloads (const Array<Download*>& downloads)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->cancelDownloads (downloads);
|
||||
#else
|
||||
ignoreUnused (downloads);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
JUCE_IMPLEMENT_SINGLETON (InAppPurchases)
|
||||
|
||||
InAppPurchases::InAppPurchases()
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
: pimpl (new Pimpl (*this))
|
||||
#endif
|
||||
{}
|
||||
|
||||
InAppPurchases::~InAppPurchases() { clearSingletonInstance(); }
|
||||
|
||||
bool InAppPurchases::isInAppPurchasesSupported() const
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
return pimpl->isInAppPurchasesSupported();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::getProductsInformation (const StringArray& productIdentifiers)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->getProductsInformation (productIdentifiers);
|
||||
#else
|
||||
Array<Product> products;
|
||||
for (auto productId : productIdentifiers)
|
||||
products.add (Product { productId, {}, {}, {}, {} });
|
||||
|
||||
listeners.call ([&] (Listener& l) { l.productsInfoReturned (products); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::purchaseProduct (const String& productIdentifier,
|
||||
const String& upgradeProductIdentifier,
|
||||
bool creditForUnusedSubscription)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->purchaseProduct (productIdentifier, upgradeProductIdentifier, creditForUnusedSubscription);
|
||||
#else
|
||||
Listener::PurchaseInfo purchaseInfo { Purchase { "", productIdentifier, {}, {}, {} }, {} };
|
||||
|
||||
listeners.call ([&] (Listener& l) { l.productPurchaseFinished (purchaseInfo, false, "In-app purchases unavailable"); });
|
||||
ignoreUnused (upgradeProductIdentifier, creditForUnusedSubscription);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::restoreProductsBoughtList (bool includeDownloadInfo, const String& subscriptionsSharedSecret)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->restoreProductsBoughtList (includeDownloadInfo, subscriptionsSharedSecret);
|
||||
#else
|
||||
listeners.call ([] (Listener& l) { l.purchasesListRestored ({}, false, "In-app purchases unavailable"); });
|
||||
ignoreUnused (includeDownloadInfo, subscriptionsSharedSecret);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::consumePurchase (const String& productIdentifier, const String& purchaseToken)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->consumePurchase (productIdentifier, purchaseToken);
|
||||
#else
|
||||
listeners.call ([&] (Listener& l) { l.productConsumed (productIdentifier, false, "In-app purchases unavailable"); });
|
||||
ignoreUnused (purchaseToken);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::addListener (Listener* l) { listeners.add (l); }
|
||||
void InAppPurchases::removeListener (Listener* l) { listeners.remove (l); }
|
||||
|
||||
void InAppPurchases::startDownloads (const Array<Download*>& downloads)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->startDownloads (downloads);
|
||||
#else
|
||||
ignoreUnused (downloads);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::pauseDownloads (const Array<Download*>& downloads)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->pauseDownloads (downloads);
|
||||
#else
|
||||
ignoreUnused (downloads);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::resumeDownloads (const Array<Download*>& downloads)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->resumeDownloads (downloads);
|
||||
#else
|
||||
ignoreUnused (downloads);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InAppPurchases::cancelDownloads (const Array<Download*>& downloads)
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
pimpl->cancelDownloads (downloads);
|
||||
#else
|
||||
ignoreUnused (downloads);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,298 +1,298 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Provides in-app purchase functionality.
|
||||
|
||||
Your app should create a single instance of this class, and on iOS it should
|
||||
be created as soon as your app starts. This is because on application startup
|
||||
any previously pending transactions will be resumed.
|
||||
|
||||
Once an InAppPurchases object is created, call addListener() to attach listeners.
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API InAppPurchases : private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
#ifndef DOXYGEN
|
||||
JUCE_DECLARE_SINGLETON (InAppPurchases, false)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a product available in the store. */
|
||||
struct Product
|
||||
{
|
||||
/** Product ID (also known as SKU) that uniquely identifies a product in the store. */
|
||||
String identifier;
|
||||
|
||||
/** Title of the product. */
|
||||
String title;
|
||||
|
||||
/** Description of the product. */
|
||||
String description;
|
||||
|
||||
/** Price of the product in local currency. */
|
||||
String price;
|
||||
|
||||
/** Price locale. */
|
||||
String priceLocale;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a purchase of a product in the store. */
|
||||
struct Purchase
|
||||
{
|
||||
/** A unique order identifier for the transaction (generated by the store). */
|
||||
String orderId;
|
||||
|
||||
/** A unique identifier of in-app product that was purchased. */
|
||||
String productId;
|
||||
|
||||
/** This will be bundle ID on iOS and package name on Android, of the application for which this
|
||||
in-app product was purchased. */
|
||||
String applicationBundleName;
|
||||
|
||||
/** Date of the purchase (in ISO8601 format). */
|
||||
String purchaseTime;
|
||||
|
||||
/** Android only: purchase token that should be used to consume purchase, provided that In-App product
|
||||
is consumable. */
|
||||
String purchaseToken;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** iOS only: represents in-app purchase download. Download will be available only
|
||||
for purchases that are hosted on the AppStore. */
|
||||
struct Download
|
||||
{
|
||||
enum class Status
|
||||
{
|
||||
waiting = 0, /**< The download is waiting to start. Called at the beginning of a download operation. */
|
||||
active, /**< The download is in progress. */
|
||||
paused, /**< The download was paused and is awaiting resuming or cancelling. */
|
||||
finished, /**< The download was finished successfully. */
|
||||
failed, /**< The download failed (e.g. because of no internet connection). */
|
||||
cancelled, /**< The download was cancelled. */
|
||||
};
|
||||
|
||||
virtual ~Download() {}
|
||||
|
||||
/** A unique identifier for the in-app product to be downloaded. */
|
||||
virtual String getProductId() const = 0;
|
||||
|
||||
/** Content length in bytes. */
|
||||
virtual int64 getContentLength() const = 0;
|
||||
|
||||
/** Content version. */
|
||||
virtual String getContentVersion() const = 0;
|
||||
|
||||
/** Returns current status of the download. */
|
||||
virtual Status getStatus() const = 0;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Represents an object that gets notified about events such as product info returned or product purchase
|
||||
finished. */
|
||||
struct Listener
|
||||
{
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called whenever a product info is returned after a call to InAppPurchases::getProductsInformation(). */
|
||||
virtual void productsInfoReturned (const Array<Product>& /*products*/) {}
|
||||
|
||||
/** Structure holding purchase information */
|
||||
struct PurchaseInfo
|
||||
{
|
||||
Purchase purchase;
|
||||
Array<Download*> downloads;
|
||||
};
|
||||
|
||||
/** Called whenever a purchase is complete, with additional state whether the purchase completed successfully.
|
||||
|
||||
For hosted content (iOS only), the downloads array within PurchaseInfo will contain all download objects corresponding
|
||||
with the purchase. For non-hosted content, the downloads array will be empty.
|
||||
|
||||
InAppPurchases class will own downloads and will delete them as soon as they are finished.
|
||||
|
||||
NOTE: It is possible to receive this callback for the same purchase multiple times. If that happens,
|
||||
only the newest set of downloads and the newest orderId will be valid, the old ones should be not used anymore!
|
||||
*/
|
||||
virtual void productPurchaseFinished (const PurchaseInfo&, bool /*success*/, const String& /*statusDescription*/) {}
|
||||
|
||||
/** Called when a list of all purchases is restored. This can be used to figure out to
|
||||
which products a user is entitled to.
|
||||
|
||||
NOTE: It is possible to receive this callback for the same purchase multiple times. If that happens,
|
||||
only the newest set of downloads and the newest orderId will be valid, the old ones should be not used anymore!
|
||||
*/
|
||||
virtual void purchasesListRestored (const Array<PurchaseInfo>&, bool /*success*/, const String& /*statusDescription*/) {}
|
||||
|
||||
/** Called whenever a product consumption finishes. */
|
||||
virtual void productConsumed (const String& /*productId*/, bool /*success*/, const String& /*statusDescription*/) {}
|
||||
|
||||
/** iOS only: Called when a product download progress gets updated. If the download was interrupted in the last
|
||||
application session, this callback may be called after the application starts.
|
||||
|
||||
If the download was in progress and the application was closed, the download may happily continue in the
|
||||
background by OS. If you open the app and the download is still in progress, you will receive this callback.
|
||||
If the download finishes in the background before you start the app again, you will receive productDownloadFinished
|
||||
callback instead. The download will only stop when it is explicitly cancelled or when it is finished.
|
||||
*/
|
||||
virtual void productDownloadProgressUpdate (Download&, float /*progress*/, RelativeTime /*timeRemaining*/) {}
|
||||
|
||||
/** iOS only: Called when a product download is paused. This may also be called after the application starts, if
|
||||
the download was in a paused state and the application was closed before finishing the download.
|
||||
|
||||
Only after the download is finished successfully or cancelled you will stop receiving this callback on startup.
|
||||
*/
|
||||
virtual void productDownloadPaused (Download&) {}
|
||||
|
||||
/** iOS only: Called when a product download finishes (successfully or not). Call Download::getStatus()
|
||||
to check if the downloaded finished successfully.
|
||||
|
||||
It is your responsibility to move the download content into your app directory and to clean up
|
||||
any files that are no longer needed.
|
||||
|
||||
After the download is finished, the download object is destroyed and should not be accessed anymore.
|
||||
*/
|
||||
virtual void productDownloadFinished (Download&, const URL& /*downloadedContentPath*/) {}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Checks whether in-app purchases is supported on current platform. On iOS this always returns true. */
|
||||
bool isInAppPurchasesSupported() const;
|
||||
|
||||
/** Asynchronously requests information for products with given ids. Upon completion, for each enquired product
|
||||
there is going to be a corresponding Product object.
|
||||
If there is no information available for the given product identifier, it will be ignored.
|
||||
*/
|
||||
void getProductsInformation (const StringArray& productIdentifiers);
|
||||
|
||||
/** Asynchronously requests to buy a product with given id.
|
||||
|
||||
@param productIdentifier The product identifier.
|
||||
|
||||
@param upgradeOrDowngradeFromSubscriptionWithProductIdentifier (Android only) specifies the subscription that will be replaced by
|
||||
the one being purchased now. Used only when buying a subscription
|
||||
that is an upgrade or downgrade from another.
|
||||
|
||||
@param creditForUnusedSubscription (Android only) controls whether a user should be credited for any unused subscription time on
|
||||
the product that is being upgraded or downgraded.
|
||||
*/
|
||||
void purchaseProduct (const String& productIdentifier,
|
||||
const String& upgradeOrDowngradeFromSubscriptionWithProductIdentifier = {},
|
||||
bool creditForUnusedSubscription = true);
|
||||
|
||||
/** Asynchronously asks about a list of products that a user has already bought. Upon completion, Listener::purchasesListReceived()
|
||||
callback will be invoked. The user may be prompted to login first.
|
||||
|
||||
@param includeDownloadInfo (iOS only) if true, then after restoration is successful, the downloads array passed to
|
||||
Listener::purchasesListReceived() callback will contain all the download objects corresponding with
|
||||
the purchase. In the opposite case, the downloads array will be empty.
|
||||
|
||||
@param subscriptionsSharedSecret (iOS only) required when not including download information and when there are
|
||||
auto-renewable subscription set up with this app. Refer to In-App-Purchase settings in the store.
|
||||
*/
|
||||
void restoreProductsBoughtList (bool includeDownloadInfo, const juce::String& subscriptionsSharedSecret = {});
|
||||
|
||||
/** Android only: asynchronously sends a request to mark a purchase with given identifier as consumed.
|
||||
To consume a product, provide product identifier as well as a purchase token that was generated when
|
||||
the product was purchased. The purchase token can also be retrieved by using getProductsInformation().
|
||||
In general if it is available on hand, it is better to use it, because otherwise another async
|
||||
request will be sent to the store, to first retrieve the token.
|
||||
|
||||
After successful consumption, a product will no longer be returned in getProductsBought() and
|
||||
it will be available for purchase.
|
||||
|
||||
On iOS consumption happens automatically. If the product was set as consumable, this function is a no-op.
|
||||
*/
|
||||
void consumePurchase (const String& productIdentifier, const String& purchaseToken = {});
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener. */
|
||||
void addListener (Listener*);
|
||||
|
||||
/** Removes a listener. */
|
||||
void removeListener (Listener*);
|
||||
|
||||
//==============================================================================
|
||||
/** iOS only: Starts downloads of hosted content from the store. */
|
||||
void startDownloads (const Array<Download*>& downloads);
|
||||
|
||||
/** iOS only: Pauses downloads of hosted content from the store. */
|
||||
void pauseDownloads (const Array<Download*>& downloads);
|
||||
|
||||
/** iOS only: Resumes downloads of hosted content from the store. */
|
||||
void resumeDownloads (const Array<Download*>& downloads);
|
||||
|
||||
/** iOS only: Cancels downloads of hosted content from the store. */
|
||||
void cancelDownloads (const Array<Download*>& downloads);
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
[[deprecated ("On Android, it is no longer necessary to specify whether the product being purchased is a subscription "
|
||||
"and only a single subscription can be upgraded/downgraded. Use the updated purchaseProduct method "
|
||||
"which takes a single String argument.")]]
|
||||
void purchaseProduct (const String& productIdentifier,
|
||||
bool isSubscription,
|
||||
const StringArray& upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers = {},
|
||||
bool creditForUnusedSubscription = true)
|
||||
{
|
||||
|
||||
ignoreUnused (isSubscription);
|
||||
purchaseProduct (productIdentifier,
|
||||
upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers[0],
|
||||
creditForUnusedSubscription);
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
InAppPurchases();
|
||||
~InAppPurchases();
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
#if JUCE_ANDROID
|
||||
friend void juce_inAppPurchaseCompleted (void*);
|
||||
#endif
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
struct Pimpl;
|
||||
friend struct Pimpl;
|
||||
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Provides in-app purchase functionality.
|
||||
|
||||
Your app should create a single instance of this class, and on iOS it should
|
||||
be created as soon as your app starts. This is because on application startup
|
||||
any previously pending transactions will be resumed.
|
||||
|
||||
Once an InAppPurchases object is created, call addListener() to attach listeners.
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API InAppPurchases : private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
#ifndef DOXYGEN
|
||||
JUCE_DECLARE_SINGLETON (InAppPurchases, false)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a product available in the store. */
|
||||
struct Product
|
||||
{
|
||||
/** Product ID (also known as SKU) that uniquely identifies a product in the store. */
|
||||
String identifier;
|
||||
|
||||
/** Title of the product. */
|
||||
String title;
|
||||
|
||||
/** Description of the product. */
|
||||
String description;
|
||||
|
||||
/** Price of the product in local currency. */
|
||||
String price;
|
||||
|
||||
/** Price locale. */
|
||||
String priceLocale;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a purchase of a product in the store. */
|
||||
struct Purchase
|
||||
{
|
||||
/** A unique order identifier for the transaction (generated by the store). */
|
||||
String orderId;
|
||||
|
||||
/** Unique identifiers of the products that were purchased. */
|
||||
StringArray productIds;
|
||||
|
||||
/** This will be bundle ID on iOS and package name on Android, of the application for which this
|
||||
in-app product was purchased. */
|
||||
String applicationBundleName;
|
||||
|
||||
/** Date of the purchase (in ISO8601 format). */
|
||||
String purchaseTime;
|
||||
|
||||
/** Android only: purchase token that should be used to consume purchase, provided that In-App product
|
||||
is consumable. */
|
||||
String purchaseToken;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** iOS only: represents in-app purchase download. Download will be available only
|
||||
for purchases that are hosted on the AppStore. */
|
||||
struct Download
|
||||
{
|
||||
enum class Status
|
||||
{
|
||||
waiting = 0, /**< The download is waiting to start. Called at the beginning of a download operation. */
|
||||
active, /**< The download is in progress. */
|
||||
paused, /**< The download was paused and is awaiting resuming or cancelling. */
|
||||
finished, /**< The download was finished successfully. */
|
||||
failed, /**< The download failed (e.g. because of no internet connection). */
|
||||
cancelled, /**< The download was cancelled. */
|
||||
};
|
||||
|
||||
virtual ~Download() {}
|
||||
|
||||
/** A unique identifier for the in-app product to be downloaded. */
|
||||
virtual String getProductId() const = 0;
|
||||
|
||||
/** Content length in bytes. */
|
||||
virtual int64 getContentLength() const = 0;
|
||||
|
||||
/** Content version. */
|
||||
virtual String getContentVersion() const = 0;
|
||||
|
||||
/** Returns current status of the download. */
|
||||
virtual Status getStatus() const = 0;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Represents an object that gets notified about events such as product info returned or product purchase
|
||||
finished. */
|
||||
struct Listener
|
||||
{
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called whenever a product info is returned after a call to InAppPurchases::getProductsInformation(). */
|
||||
virtual void productsInfoReturned (const Array<Product>& /*products*/) {}
|
||||
|
||||
/** Structure holding purchase information */
|
||||
struct PurchaseInfo
|
||||
{
|
||||
Purchase purchase;
|
||||
Array<Download*> downloads;
|
||||
};
|
||||
|
||||
/** Called whenever a purchase is complete, with additional state whether the purchase completed successfully.
|
||||
|
||||
For hosted content (iOS only), the downloads array within PurchaseInfo will contain all download objects corresponding
|
||||
with the purchase. For non-hosted content, the downloads array will be empty.
|
||||
|
||||
InAppPurchases class will own downloads and will delete them as soon as they are finished.
|
||||
|
||||
NOTE: It is possible to receive this callback for the same purchase multiple times. If that happens,
|
||||
only the newest set of downloads and the newest orderId will be valid, the old ones should be not used anymore!
|
||||
*/
|
||||
virtual void productPurchaseFinished (const PurchaseInfo&, bool /*success*/, const String& /*statusDescription*/) {}
|
||||
|
||||
/** Called when a list of all purchases is restored. This can be used to figure out to
|
||||
which products a user is entitled.
|
||||
|
||||
NOTE: It is possible to receive this callback for the same purchase multiple times. If that happens,
|
||||
only the newest set of downloads and the newest orderId will be valid, the old ones should be not used anymore!
|
||||
*/
|
||||
virtual void purchasesListRestored (const Array<PurchaseInfo>&, bool /*success*/, const String& /*statusDescription*/) {}
|
||||
|
||||
/** Called whenever a product consumption finishes. */
|
||||
virtual void productConsumed (const String& /*productId*/, bool /*success*/, const String& /*statusDescription*/) {}
|
||||
|
||||
/** iOS only: Called when a product download progress gets updated. If the download was interrupted in the last
|
||||
application session, this callback may be called after the application starts.
|
||||
|
||||
If the download was in progress and the application was closed, the download may happily continue in the
|
||||
background by OS. If you open the app and the download is still in progress, you will receive this callback.
|
||||
If the download finishes in the background before you start the app again, you will receive productDownloadFinished
|
||||
callback instead. The download will only stop when it is explicitly cancelled or when it is finished.
|
||||
*/
|
||||
virtual void productDownloadProgressUpdate (Download&, float /*progress*/, RelativeTime /*timeRemaining*/) {}
|
||||
|
||||
/** iOS only: Called when a product download is paused. This may also be called after the application starts, if
|
||||
the download was in a paused state and the application was closed before finishing the download.
|
||||
|
||||
Only after the download is finished successfully or cancelled you will stop receiving this callback on startup.
|
||||
*/
|
||||
virtual void productDownloadPaused (Download&) {}
|
||||
|
||||
/** iOS only: Called when a product download finishes (successfully or not). Call Download::getStatus()
|
||||
to check if the downloaded finished successfully.
|
||||
|
||||
It is your responsibility to move the download content into your app directory and to clean up
|
||||
any files that are no longer needed.
|
||||
|
||||
After the download is finished, the download object is destroyed and should not be accessed anymore.
|
||||
*/
|
||||
virtual void productDownloadFinished (Download&, const URL& /*downloadedContentPath*/) {}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Checks whether in-app purchases is supported on current platform. On iOS this always returns true. */
|
||||
bool isInAppPurchasesSupported() const;
|
||||
|
||||
/** Asynchronously requests information for products with given ids. Upon completion, for each enquired product
|
||||
there is going to be a corresponding Product object.
|
||||
If there is no information available for the given product identifier, it will be ignored.
|
||||
*/
|
||||
void getProductsInformation (const StringArray& productIdentifiers);
|
||||
|
||||
/** Asynchronously requests to buy a product with given id.
|
||||
|
||||
@param productIdentifier The product identifier.
|
||||
|
||||
@param upgradeOrDowngradeFromSubscriptionWithProductIdentifier (Android only) specifies the subscription that will be replaced by
|
||||
the one being purchased now. Used only when buying a subscription
|
||||
that is an upgrade or downgrade from another.
|
||||
|
||||
@param creditForUnusedSubscription (Android only) controls whether a user should be credited for any unused subscription time on
|
||||
the product that is being upgraded or downgraded.
|
||||
*/
|
||||
void purchaseProduct (const String& productIdentifier,
|
||||
const String& upgradeOrDowngradeFromSubscriptionWithProductIdentifier = {},
|
||||
bool creditForUnusedSubscription = true);
|
||||
|
||||
/** Asynchronously asks about a list of products that a user has already bought. Upon completion, Listener::purchasesListRestored()
|
||||
callback will be invoked. The user may be prompted to login first.
|
||||
|
||||
@param includeDownloadInfo (iOS only) if true, then after restoration is successful, the downloads array passed to
|
||||
Listener::purchasesListReceived() callback will contain all the download objects corresponding with
|
||||
the purchase. In the opposite case, the downloads array will be empty.
|
||||
|
||||
@param subscriptionsSharedSecret (iOS only) required when not including download information and when there are
|
||||
auto-renewable subscription set up with this app. Refer to In-App-Purchase settings in the store.
|
||||
*/
|
||||
void restoreProductsBoughtList (bool includeDownloadInfo, const juce::String& subscriptionsSharedSecret = {});
|
||||
|
||||
/** Android only: asynchronously sends a request to mark a purchase with given identifier as consumed.
|
||||
To consume a product, provide product identifier as well as a purchase token that was generated when
|
||||
the product was purchased. The purchase token can also be retrieved by using getProductsInformation().
|
||||
In general if it is available on hand, it is better to use it, because otherwise another async
|
||||
request will be sent to the store, to first retrieve the token.
|
||||
|
||||
After successful consumption, a product will no longer be returned in getProductsBought() and
|
||||
it will be available for purchase.
|
||||
|
||||
On iOS consumption happens automatically. If the product was set as consumable, this function is a no-op.
|
||||
*/
|
||||
void consumePurchase (const String& productIdentifier, const String& purchaseToken = {});
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener. */
|
||||
void addListener (Listener*);
|
||||
|
||||
/** Removes a listener. */
|
||||
void removeListener (Listener*);
|
||||
|
||||
//==============================================================================
|
||||
/** iOS only: Starts downloads of hosted content from the store. */
|
||||
void startDownloads (const Array<Download*>& downloads);
|
||||
|
||||
/** iOS only: Pauses downloads of hosted content from the store. */
|
||||
void pauseDownloads (const Array<Download*>& downloads);
|
||||
|
||||
/** iOS only: Resumes downloads of hosted content from the store. */
|
||||
void resumeDownloads (const Array<Download*>& downloads);
|
||||
|
||||
/** iOS only: Cancels downloads of hosted content from the store. */
|
||||
void cancelDownloads (const Array<Download*>& downloads);
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
[[deprecated ("On Android, it is no longer necessary to specify whether the product being purchased is a subscription "
|
||||
"and only a single subscription can be upgraded/downgraded. Use the updated purchaseProduct method "
|
||||
"which takes a single String argument.")]]
|
||||
void purchaseProduct (const String& productIdentifier,
|
||||
bool isSubscription,
|
||||
const StringArray& upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers = {},
|
||||
bool creditForUnusedSubscription = true)
|
||||
{
|
||||
|
||||
ignoreUnused (isSubscription);
|
||||
purchaseProduct (productIdentifier,
|
||||
upgradeOrDowngradeFromSubscriptionsWithProductIdentifiers[0],
|
||||
creditForUnusedSubscription);
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
InAppPurchases();
|
||||
~InAppPurchases();
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
#if JUCE_ANDROID
|
||||
friend void juce_inAppPurchaseCompleted (void*);
|
||||
#endif
|
||||
|
||||
#if JUCE_ANDROID || JUCE_IOS || JUCE_MAC
|
||||
struct Pimpl;
|
||||
friend struct Pimpl;
|
||||
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,68 +1,68 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifdef JUCE_PRODUCT_UNLOCKING_H_INCLUDED
|
||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've
|
||||
already included any other headers - just put it inside a file on its own, possibly with your config
|
||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
|
||||
header files that the compiler may be using.
|
||||
*/
|
||||
#error "Incorrect use of JUCE cpp file"
|
||||
#endif
|
||||
|
||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
|
||||
|
||||
// Set this flag to 1 to use test servers on iOS
|
||||
#ifndef JUCE_IN_APP_PURCHASES_USE_SANDBOX_ENVIRONMENT
|
||||
#define JUCE_IN_APP_PURCHASES_USE_SANDBOX_ENVIRONMENT 0
|
||||
#endif
|
||||
|
||||
#include "juce_product_unlocking.h"
|
||||
|
||||
#if JUCE_IOS || JUCE_MAC
|
||||
#import <StoreKit/StoreKit.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_IN_APP_PURCHASES
|
||||
#if JUCE_ANDROID
|
||||
#include "native/juce_android_InAppPurchases.cpp"
|
||||
#elif JUCE_IOS || JUCE_MAC
|
||||
#include "native/juce_ios_InAppPurchases.cpp"
|
||||
#endif
|
||||
|
||||
#include "in_app_purchases/juce_InAppPurchases.cpp"
|
||||
#endif
|
||||
|
||||
#include "marketplace/juce_OnlineUnlockStatus.cpp"
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_data_structures
|
||||
#include "marketplace/juce_TracktionMarketplaceStatus.cpp"
|
||||
#endif
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include "marketplace/juce_OnlineUnlockForm.cpp"
|
||||
#endif
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifdef JUCE_PRODUCT_UNLOCKING_H_INCLUDED
|
||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've
|
||||
already included any other headers - just put it inside a file on its own, possibly with your config
|
||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
|
||||
header files that the compiler may be using.
|
||||
*/
|
||||
#error "Incorrect use of JUCE cpp file"
|
||||
#endif
|
||||
|
||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
|
||||
|
||||
// Set this flag to 1 to use test servers on iOS
|
||||
#ifndef JUCE_IN_APP_PURCHASES_USE_SANDBOX_ENVIRONMENT
|
||||
#define JUCE_IN_APP_PURCHASES_USE_SANDBOX_ENVIRONMENT 0
|
||||
#endif
|
||||
|
||||
#include "juce_product_unlocking.h"
|
||||
|
||||
#if JUCE_IOS || JUCE_MAC
|
||||
#import <StoreKit/StoreKit.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_IN_APP_PURCHASES
|
||||
#if JUCE_ANDROID
|
||||
#include "native/juce_android_InAppPurchases.cpp"
|
||||
#elif JUCE_IOS || JUCE_MAC
|
||||
#include "native/juce_ios_InAppPurchases.cpp"
|
||||
#endif
|
||||
|
||||
#include "in_app_purchases/juce_InAppPurchases.cpp"
|
||||
#endif
|
||||
|
||||
#include "marketplace/juce_OnlineUnlockStatus.cpp"
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_data_structures
|
||||
#include "marketplace/juce_TracktionMarketplaceStatus.cpp"
|
||||
#endif
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include "marketplace/juce_OnlineUnlockForm.cpp"
|
||||
#endif
|
||||
|
@ -1,95 +1,95 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this module, and is read by
|
||||
the Projucer to automatically generate project code that uses it.
|
||||
For details about the syntax and how to create or use a module, see the
|
||||
JUCE Module Format.md file.
|
||||
|
||||
|
||||
BEGIN_JUCE_MODULE_DECLARATION
|
||||
|
||||
ID: juce_product_unlocking
|
||||
vendor: juce
|
||||
version: 6.1.2
|
||||
name: JUCE Online marketplace support
|
||||
description: Classes for online product authentication
|
||||
website: http://www.juce.com/juce
|
||||
license: GPL/Commercial
|
||||
minimumCppStandard: 14
|
||||
|
||||
dependencies: juce_cryptography
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
#define JUCE_PRODUCT_UNLOCKING_H_INCLUDED
|
||||
|
||||
/**
|
||||
The juce_product_unlocking module provides simple user-registration classes
|
||||
for allowing you to build apps/plugins with features that are unlocked by a
|
||||
user having a suitable account on a webserver.
|
||||
|
||||
Although originally designed for use with products that are sold on the
|
||||
Tracktion Marketplace web-store, the module itself is fully open, and can
|
||||
be used to connect to your own web-store instead, if you implement your
|
||||
own compatible web-server back-end.
|
||||
|
||||
In additional, the module supports in-app purchases both on iOS and Android
|
||||
platforms.
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
#include <juce_core/juce_core.h>
|
||||
#include <juce_cryptography/juce_cryptography.h>
|
||||
#include <juce_events/juce_events.h>
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_data_structures
|
||||
#include <juce_data_structures/juce_data_structures.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include <juce_gui_extra/juce_gui_extra.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_IN_APP_PURCHASES
|
||||
#include "in_app_purchases/juce_InAppPurchases.h"
|
||||
#endif
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_data_structures
|
||||
#include "marketplace/juce_OnlineUnlockStatus.h"
|
||||
#include "marketplace/juce_TracktionMarketplaceStatus.h"
|
||||
#endif
|
||||
|
||||
#include "marketplace/juce_KeyFileGeneration.h"
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include "marketplace/juce_OnlineUnlockForm.h"
|
||||
#endif
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this module, and is read by
|
||||
the Projucer to automatically generate project code that uses it.
|
||||
For details about the syntax and how to create or use a module, see the
|
||||
JUCE Module Format.md file.
|
||||
|
||||
|
||||
BEGIN_JUCE_MODULE_DECLARATION
|
||||
|
||||
ID: juce_product_unlocking
|
||||
vendor: juce
|
||||
version: 7.0.2
|
||||
name: JUCE Online marketplace support
|
||||
description: Classes for online product authentication
|
||||
website: http://www.juce.com/juce
|
||||
license: GPL/Commercial
|
||||
minimumCppStandard: 14
|
||||
|
||||
dependencies: juce_cryptography
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
#define JUCE_PRODUCT_UNLOCKING_H_INCLUDED
|
||||
|
||||
/**
|
||||
The juce_product_unlocking module provides simple user-registration classes
|
||||
for allowing you to build apps/plugins with features that are unlocked by a
|
||||
user having a suitable account on a webserver.
|
||||
|
||||
Although originally designed for use with products that are sold on the
|
||||
Tracktion Marketplace web-store, the module itself is fully open, and can
|
||||
be used to connect to your own web-store instead, if you implement your
|
||||
own compatible web-server back-end.
|
||||
|
||||
In additional, the module supports in-app purchases both on iOS and Android
|
||||
platforms.
|
||||
*/
|
||||
|
||||
//==============================================================================
|
||||
#include <juce_core/juce_core.h>
|
||||
#include <juce_cryptography/juce_cryptography.h>
|
||||
#include <juce_events/juce_events.h>
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_data_structures
|
||||
#include <juce_data_structures/juce_data_structures.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include <juce_gui_extra/juce_gui_extra.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_IN_APP_PURCHASES
|
||||
#include "in_app_purchases/juce_InAppPurchases.h"
|
||||
#endif
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_data_structures
|
||||
#include "marketplace/juce_OnlineUnlockStatus.h"
|
||||
#include "marketplace/juce_TracktionMarketplaceStatus.h"
|
||||
#endif
|
||||
|
||||
#include "marketplace/juce_KeyFileGeneration.h"
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
#include "marketplace/juce_OnlineUnlockForm.h"
|
||||
#endif
|
||||
|
@ -2,15 +2,15 @@
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
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-6-licence
|
||||
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
|
||||
|
@ -1,115 +1,115 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Contains static utilities for generating key-files that can be unlocked by
|
||||
the OnlineUnlockStatus class.
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API KeyGeneration
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Generates the content of a key-file which can be sent to a user's machine to
|
||||
unlock a product.
|
||||
|
||||
The returned value is a block of text containing an RSA-encoded block, followed
|
||||
by some human-readable details. If you pass this block of text to
|
||||
OnlineUnlockStatus::applyKeyFile(), it will decrypt it, and if the
|
||||
key matches and the machine numbers match, it will unlock that machine.
|
||||
|
||||
Typically the way you'd use this on a server would be to build a small executable
|
||||
that simply calls this method and prints the result, so that the webserver can
|
||||
use this as a reply to the product's auto-registration mechanism. The
|
||||
keyGenerationAppMain() function is an example of how to build such a function.
|
||||
|
||||
@see OnlineUnlockStatus
|
||||
*/
|
||||
static String JUCE_CALLTYPE generateKeyFile (const String& appName,
|
||||
const String& userEmail,
|
||||
const String& userName,
|
||||
const String& machineNumbers,
|
||||
const RSAKey& privateKey);
|
||||
|
||||
/** Similar to the above key file generation method but with an expiry time.
|
||||
You must supply a Time after which this key file should no longer be considered as active.
|
||||
|
||||
N.B. when an app is unlocked with an expiring key file, OnlineUnlockStatus::isUnlocked will
|
||||
still return false. You must then check OnlineUnlockStatus::getExpiryTime to see if this
|
||||
expiring key file is still in date and act accordingly.
|
||||
|
||||
@see OnlineUnlockStatus
|
||||
*/
|
||||
static String JUCE_CALLTYPE generateExpiringKeyFile (const String& appName,
|
||||
const String& userEmail,
|
||||
const String& userName,
|
||||
const String& machineNumbers,
|
||||
const Time expiryTime,
|
||||
const RSAKey& privateKey);
|
||||
|
||||
//==============================================================================
|
||||
/** This is a simple implementation of a key-generator that you could easily wrap in
|
||||
a command-line main() function for use on your server.
|
||||
|
||||
So for example you might use this in a command line app called "unlocker" and
|
||||
then call it like this:
|
||||
|
||||
unlocker MyGreatApp Joe_Bloggs joebloggs@foobar.com 1234abcd,95432ff 22d9aec92d986dd1,923ad49e9e7ff294c
|
||||
*/
|
||||
static int keyGenerationAppMain (int argc, char* argv[])
|
||||
{
|
||||
StringArray args;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
args.add (argv[i]);
|
||||
|
||||
if (args.size() != 5)
|
||||
{
|
||||
std::cout << "Requires 5 arguments: app-name user-email username machine-numbers private-key" << std::endl
|
||||
<< " app-name: name of the product being unlocked" << std::endl
|
||||
<< " user-email: user's email address" << std::endl
|
||||
<< " username: name of the user. Careful not to allow any spaces!" << std::endl
|
||||
<< " machine-numbers: a comma- or semicolon-separated list of all machine ID strings this user can run this product on (no whitespace between items!)" << std::endl
|
||||
<< " private-key: the RSA private key corresponding to the public key you've used in the app" << std::endl
|
||||
<< std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (! args[4].containsChar (','))
|
||||
{
|
||||
std::cout << "Not a valid RSA key!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << generateKeyFile (args[0], args[1], args[2], args[3], RSAKey (args[4])) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Contains static utilities for generating key-files that can be unlocked by
|
||||
the OnlineUnlockStatus class.
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API KeyGeneration
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Generates the content of a key-file which can be sent to a user's machine to
|
||||
unlock a product.
|
||||
|
||||
The returned value is a block of text containing an RSA-encoded block, followed
|
||||
by some human-readable details. If you pass this block of text to
|
||||
OnlineUnlockStatus::applyKeyFile(), it will decrypt it, and if the
|
||||
key matches and the machine numbers match, it will unlock that machine.
|
||||
|
||||
Typically the way you'd use this on a server would be to build a small executable
|
||||
that simply calls this method and prints the result, so that the webserver can
|
||||
use this as a reply to the product's auto-registration mechanism. The
|
||||
keyGenerationAppMain() function is an example of how to build such a function.
|
||||
|
||||
@see OnlineUnlockStatus
|
||||
*/
|
||||
static String JUCE_CALLTYPE generateKeyFile (const String& appName,
|
||||
const String& userEmail,
|
||||
const String& userName,
|
||||
const String& machineNumbers,
|
||||
const RSAKey& privateKey);
|
||||
|
||||
/** Similar to the above key file generation method but with an expiry time.
|
||||
You must supply a Time after which this key file should no longer be considered as active.
|
||||
|
||||
N.B. when an app is unlocked with an expiring key file, OnlineUnlockStatus::isUnlocked will
|
||||
still return false. You must then check OnlineUnlockStatus::getExpiryTime to see if this
|
||||
expiring key file is still in date and act accordingly.
|
||||
|
||||
@see OnlineUnlockStatus
|
||||
*/
|
||||
static String JUCE_CALLTYPE generateExpiringKeyFile (const String& appName,
|
||||
const String& userEmail,
|
||||
const String& userName,
|
||||
const String& machineNumbers,
|
||||
const Time expiryTime,
|
||||
const RSAKey& privateKey);
|
||||
|
||||
//==============================================================================
|
||||
/** This is a simple implementation of a key-generator that you could easily wrap in
|
||||
a command-line main() function for use on your server.
|
||||
|
||||
So for example you might use this in a command line app called "unlocker" and
|
||||
then call it like this:
|
||||
|
||||
unlocker MyGreatApp joebloggs@foobar.com Joe_Bloggs 1234abcd,95432ff 22d9aec92d986dd1,923ad49e9e7ff294c
|
||||
*/
|
||||
static int keyGenerationAppMain (int argc, char* argv[])
|
||||
{
|
||||
StringArray args;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
args.add (argv[i]);
|
||||
|
||||
if (args.size() != 5)
|
||||
{
|
||||
std::cout << "Requires 5 arguments: app-name user-email username machine-numbers private-key" << std::endl
|
||||
<< " app-name: name of the product being unlocked" << std::endl
|
||||
<< " user-email: user's email address" << std::endl
|
||||
<< " username: name of the user. Careful not to allow any spaces!" << std::endl
|
||||
<< " machine-numbers: a comma- or semicolon-separated list of all machine ID strings this user can run this product on (no whitespace between items!)" << std::endl
|
||||
<< " private-key: the RSA private key corresponding to the public key you've used in the app" << std::endl
|
||||
<< std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (! args[4].containsChar (','))
|
||||
{
|
||||
std::cout << "Not a valid RSA key!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << generateKeyFile (args[0], args[1], args[2], args[3], RSAKey (args[4])) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,319 +1,319 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct Spinner : public Component,
|
||||
private Timer
|
||||
{
|
||||
Spinner() { startTimer (1000 / 50); }
|
||||
void timerCallback() override { repaint(); }
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey, 0, 0, getWidth(), getHeight());
|
||||
}
|
||||
};
|
||||
|
||||
struct OnlineUnlockForm::OverlayComp : public Component,
|
||||
private Thread,
|
||||
private Timer,
|
||||
private Button::Listener
|
||||
{
|
||||
OverlayComp (OnlineUnlockForm& f, bool hasCancelButton = false)
|
||||
: Thread (String()), form (f)
|
||||
{
|
||||
result.succeeded = false;
|
||||
email = form.emailBox.getText();
|
||||
password = form.passwordBox.getText();
|
||||
addAndMakeVisible (spinner);
|
||||
|
||||
if (hasCancelButton)
|
||||
{
|
||||
cancelButton.reset (new TextButton (TRANS ("Cancel")));
|
||||
addAndMakeVisible (cancelButton.get());
|
||||
cancelButton->addListener (this);
|
||||
}
|
||||
|
||||
startThread (4);
|
||||
}
|
||||
|
||||
~OverlayComp() override
|
||||
{
|
||||
stopThread (10000);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (Colours::white.withAlpha (0.97f));
|
||||
|
||||
g.setColour (Colours::black);
|
||||
g.setFont (15.0f);
|
||||
|
||||
g.drawFittedText (TRANS("Contacting XYZ...").replace ("XYZ", form.status.getWebsiteName()),
|
||||
getLocalBounds().reduced (20, 0).removeFromTop (proportionOfHeight (0.6f)),
|
||||
Justification::centred, 5);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
const int spinnerSize = 40;
|
||||
spinner.setBounds ((getWidth() - spinnerSize) / 2, proportionOfHeight (0.6f), spinnerSize, spinnerSize);
|
||||
|
||||
if (cancelButton != nullptr)
|
||||
cancelButton->setBounds (getLocalBounds().removeFromBottom (50).reduced (getWidth() / 4, 5));
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
result = form.status.attemptWebserverUnlock (email, password);
|
||||
startTimer (100);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
spinner.setVisible (false);
|
||||
stopTimer();
|
||||
|
||||
if (result.errorMessage.isNotEmpty())
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
TRANS("Registration Failed"),
|
||||
result.errorMessage);
|
||||
}
|
||||
else if (result.informativeMessage.isNotEmpty())
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
|
||||
TRANS("Registration Complete!"),
|
||||
result.informativeMessage);
|
||||
}
|
||||
else if (result.urlToLaunch.isNotEmpty())
|
||||
{
|
||||
URL url (result.urlToLaunch);
|
||||
url.launchInDefaultBrowser();
|
||||
}
|
||||
|
||||
// (local copies because we're about to delete this)
|
||||
const bool worked = result.succeeded;
|
||||
OnlineUnlockForm& f = form;
|
||||
|
||||
delete this;
|
||||
|
||||
if (worked)
|
||||
f.dismiss();
|
||||
}
|
||||
|
||||
void buttonClicked (Button* button) override
|
||||
{
|
||||
if (button == cancelButton.get())
|
||||
{
|
||||
form.status.userCancelled();
|
||||
|
||||
spinner.setVisible (false);
|
||||
stopTimer();
|
||||
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
OnlineUnlockForm& form;
|
||||
Spinner spinner;
|
||||
OnlineUnlockStatus::UnlockResult result;
|
||||
String email, password;
|
||||
|
||||
std::unique_ptr<TextButton> cancelButton;
|
||||
|
||||
JUCE_LEAK_DETECTOR (OnlineUnlockForm::OverlayComp)
|
||||
};
|
||||
|
||||
static juce_wchar getDefaultPasswordChar() noexcept
|
||||
{
|
||||
#if JUCE_LINUX || JUCE_BSD
|
||||
return 0x2022;
|
||||
#else
|
||||
return 0x25cf;
|
||||
#endif
|
||||
}
|
||||
|
||||
OnlineUnlockForm::OnlineUnlockForm (OnlineUnlockStatus& s,
|
||||
const String& userInstructions,
|
||||
bool hasCancelButton,
|
||||
bool overlayHasCancelButton)
|
||||
: message (String(), userInstructions),
|
||||
passwordBox (String(), getDefaultPasswordChar()),
|
||||
registerButton (TRANS("Register")),
|
||||
cancelButton (TRANS ("Cancel")),
|
||||
status (s),
|
||||
showOverlayCancelButton (overlayHasCancelButton)
|
||||
{
|
||||
// Please supply a message to tell your users what to do!
|
||||
jassert (userInstructions.isNotEmpty());
|
||||
|
||||
setOpaque (true);
|
||||
|
||||
emailBox.setText (status.getUserEmail());
|
||||
message.setJustificationType (Justification::centred);
|
||||
|
||||
addAndMakeVisible (message);
|
||||
addAndMakeVisible (emailBox);
|
||||
addAndMakeVisible (passwordBox);
|
||||
addAndMakeVisible (registerButton);
|
||||
|
||||
if (hasCancelButton)
|
||||
addAndMakeVisible (cancelButton);
|
||||
|
||||
emailBox.setEscapeAndReturnKeysConsumed (false);
|
||||
passwordBox.setEscapeAndReturnKeysConsumed (false);
|
||||
|
||||
registerButton.addShortcut (KeyPress (KeyPress::returnKey));
|
||||
|
||||
registerButton.addListener (this);
|
||||
cancelButton.addListener (this);
|
||||
|
||||
lookAndFeelChanged();
|
||||
setSize (500, 250);
|
||||
}
|
||||
|
||||
OnlineUnlockForm::~OnlineUnlockForm()
|
||||
{
|
||||
unlockingOverlay.deleteAndZero();
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (Colours::lightgrey);
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::resized()
|
||||
{
|
||||
/* If you're writing a plugin, then DO NOT USE A POP-UP A DIALOG WINDOW!
|
||||
Plugins that create external windows are incredibly annoying for users, and
|
||||
cause all sorts of headaches for hosts. Don't be the person who writes that
|
||||
plugin that irritates everyone with a nagging dialog box every time they scan!
|
||||
*/
|
||||
jassert (JUCEApplicationBase::isStandaloneApp() || findParentComponentOfClass<DialogWindow>() == nullptr);
|
||||
|
||||
const int buttonHeight = 22;
|
||||
|
||||
auto r = getLocalBounds().reduced (10, 20);
|
||||
|
||||
auto buttonArea = r.removeFromBottom (buttonHeight);
|
||||
registerButton.changeWidthToFitText (buttonHeight);
|
||||
cancelButton.changeWidthToFitText (buttonHeight);
|
||||
|
||||
const int gap = 20;
|
||||
buttonArea = buttonArea.withSizeKeepingCentre (registerButton.getWidth()
|
||||
+ (cancelButton.isVisible() ? gap + cancelButton.getWidth() : 0),
|
||||
buttonHeight);
|
||||
registerButton.setBounds (buttonArea.removeFromLeft (registerButton.getWidth()));
|
||||
buttonArea.removeFromLeft (gap);
|
||||
cancelButton.setBounds (buttonArea);
|
||||
|
||||
r.removeFromBottom (20);
|
||||
|
||||
// (force use of a default system font to make sure it has the password blob character)
|
||||
Font font (Font::getDefaultTypefaceForFont (Font (Font::getDefaultSansSerifFontName(),
|
||||
Font::getDefaultStyle(),
|
||||
5.0f)));
|
||||
|
||||
const int boxHeight = 24;
|
||||
passwordBox.setBounds (r.removeFromBottom (boxHeight));
|
||||
passwordBox.setInputRestrictions (64);
|
||||
passwordBox.setFont (font);
|
||||
|
||||
r.removeFromBottom (20);
|
||||
emailBox.setBounds (r.removeFromBottom (boxHeight));
|
||||
emailBox.setInputRestrictions (512);
|
||||
emailBox.setFont (font);
|
||||
|
||||
r.removeFromBottom (20);
|
||||
|
||||
message.setBounds (r);
|
||||
|
||||
if (unlockingOverlay != nullptr)
|
||||
unlockingOverlay->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::lookAndFeelChanged()
|
||||
{
|
||||
Colour labelCol (findColour (TextEditor::backgroundColourId).contrasting (0.5f));
|
||||
|
||||
emailBox.setTextToShowWhenEmpty (TRANS("Email Address"), labelCol);
|
||||
passwordBox.setTextToShowWhenEmpty (TRANS("Password"), labelCol);
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::showBubbleMessage (const String& text, Component& target)
|
||||
{
|
||||
bubble.reset (new BubbleMessageComponent (500));
|
||||
addChildComponent (bubble.get());
|
||||
|
||||
AttributedString attString;
|
||||
attString.append (text, Font (16.0f));
|
||||
|
||||
bubble->showAt (getLocalArea (&target, target.getLocalBounds()),
|
||||
attString, 500, // numMillisecondsBeforeRemoving
|
||||
true, // removeWhenMouseClicked
|
||||
false); // deleteSelfAfterUse
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::buttonClicked (Button* b)
|
||||
{
|
||||
if (b == ®isterButton)
|
||||
attemptRegistration();
|
||||
else if (b == &cancelButton)
|
||||
dismiss();
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::attemptRegistration()
|
||||
{
|
||||
if (unlockingOverlay == nullptr)
|
||||
{
|
||||
if (emailBox.getText().trim().length() < 3)
|
||||
{
|
||||
showBubbleMessage (TRANS ("Please enter a valid email address!"), emailBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordBox.getText().trim().length() < 3)
|
||||
{
|
||||
showBubbleMessage (TRANS ("Please enter a valid password!"), passwordBox);
|
||||
return;
|
||||
}
|
||||
|
||||
status.setUserEmail (emailBox.getText());
|
||||
|
||||
addAndMakeVisible (unlockingOverlay = new OverlayComp (*this, showOverlayCancelButton));
|
||||
resized();
|
||||
unlockingOverlay->enterModalState();
|
||||
}
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::dismiss()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct Spinner : public Component,
|
||||
private Timer
|
||||
{
|
||||
Spinner() { startTimer (1000 / 50); }
|
||||
void timerCallback() override { repaint(); }
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey, 0, 0, getWidth(), getHeight());
|
||||
}
|
||||
};
|
||||
|
||||
struct OnlineUnlockForm::OverlayComp : public Component,
|
||||
private Thread,
|
||||
private Timer,
|
||||
private Button::Listener
|
||||
{
|
||||
OverlayComp (OnlineUnlockForm& f, bool hasCancelButton = false)
|
||||
: Thread (String()), form (f)
|
||||
{
|
||||
result.succeeded = false;
|
||||
email = form.emailBox.getText();
|
||||
password = form.passwordBox.getText();
|
||||
addAndMakeVisible (spinner);
|
||||
|
||||
if (hasCancelButton)
|
||||
{
|
||||
cancelButton.reset (new TextButton (TRANS ("Cancel")));
|
||||
addAndMakeVisible (cancelButton.get());
|
||||
cancelButton->addListener (this);
|
||||
}
|
||||
|
||||
startThread (4);
|
||||
}
|
||||
|
||||
~OverlayComp() override
|
||||
{
|
||||
stopThread (10000);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (Colours::white.withAlpha (0.97f));
|
||||
|
||||
g.setColour (Colours::black);
|
||||
g.setFont (15.0f);
|
||||
|
||||
g.drawFittedText (TRANS("Contacting XYZ...").replace ("XYZ", form.status.getWebsiteName()),
|
||||
getLocalBounds().reduced (20, 0).removeFromTop (proportionOfHeight (0.6f)),
|
||||
Justification::centred, 5);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
const int spinnerSize = 40;
|
||||
spinner.setBounds ((getWidth() - spinnerSize) / 2, proportionOfHeight (0.6f), spinnerSize, spinnerSize);
|
||||
|
||||
if (cancelButton != nullptr)
|
||||
cancelButton->setBounds (getLocalBounds().removeFromBottom (50).reduced (getWidth() / 4, 5));
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
result = form.status.attemptWebserverUnlock (email, password);
|
||||
startTimer (100);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
spinner.setVisible (false);
|
||||
stopTimer();
|
||||
|
||||
if (result.errorMessage.isNotEmpty())
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||||
TRANS("Registration Failed"),
|
||||
result.errorMessage);
|
||||
}
|
||||
else if (result.informativeMessage.isNotEmpty())
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
|
||||
TRANS("Registration Complete!"),
|
||||
result.informativeMessage);
|
||||
}
|
||||
else if (result.urlToLaunch.isNotEmpty())
|
||||
{
|
||||
URL url (result.urlToLaunch);
|
||||
url.launchInDefaultBrowser();
|
||||
}
|
||||
|
||||
// (local copies because we're about to delete this)
|
||||
const bool worked = result.succeeded;
|
||||
OnlineUnlockForm& f = form;
|
||||
|
||||
delete this;
|
||||
|
||||
if (worked)
|
||||
f.dismiss();
|
||||
}
|
||||
|
||||
void buttonClicked (Button* button) override
|
||||
{
|
||||
if (button == cancelButton.get())
|
||||
{
|
||||
form.status.userCancelled();
|
||||
|
||||
spinner.setVisible (false);
|
||||
stopTimer();
|
||||
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
OnlineUnlockForm& form;
|
||||
Spinner spinner;
|
||||
OnlineUnlockStatus::UnlockResult result;
|
||||
String email, password;
|
||||
|
||||
std::unique_ptr<TextButton> cancelButton;
|
||||
|
||||
JUCE_LEAK_DETECTOR (OnlineUnlockForm::OverlayComp)
|
||||
};
|
||||
|
||||
static juce_wchar getDefaultPasswordChar() noexcept
|
||||
{
|
||||
#if JUCE_LINUX || JUCE_BSD
|
||||
return 0x2022;
|
||||
#else
|
||||
return 0x25cf;
|
||||
#endif
|
||||
}
|
||||
|
||||
OnlineUnlockForm::OnlineUnlockForm (OnlineUnlockStatus& s,
|
||||
const String& userInstructions,
|
||||
bool hasCancelButton,
|
||||
bool overlayHasCancelButton)
|
||||
: message (String(), userInstructions),
|
||||
passwordBox (String(), getDefaultPasswordChar()),
|
||||
registerButton (TRANS("Register")),
|
||||
cancelButton (TRANS ("Cancel")),
|
||||
status (s),
|
||||
showOverlayCancelButton (overlayHasCancelButton)
|
||||
{
|
||||
// Please supply a message to tell your users what to do!
|
||||
jassert (userInstructions.isNotEmpty());
|
||||
|
||||
setOpaque (true);
|
||||
|
||||
emailBox.setText (status.getUserEmail());
|
||||
message.setJustificationType (Justification::centred);
|
||||
|
||||
addAndMakeVisible (message);
|
||||
addAndMakeVisible (emailBox);
|
||||
addAndMakeVisible (passwordBox);
|
||||
addAndMakeVisible (registerButton);
|
||||
|
||||
if (hasCancelButton)
|
||||
addAndMakeVisible (cancelButton);
|
||||
|
||||
emailBox.setEscapeAndReturnKeysConsumed (false);
|
||||
passwordBox.setEscapeAndReturnKeysConsumed (false);
|
||||
|
||||
registerButton.addShortcut (KeyPress (KeyPress::returnKey));
|
||||
|
||||
registerButton.addListener (this);
|
||||
cancelButton.addListener (this);
|
||||
|
||||
lookAndFeelChanged();
|
||||
setSize (500, 250);
|
||||
}
|
||||
|
||||
OnlineUnlockForm::~OnlineUnlockForm()
|
||||
{
|
||||
unlockingOverlay.deleteAndZero();
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (Colours::lightgrey);
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::resized()
|
||||
{
|
||||
/* If you're writing a plugin, then DO NOT USE A POP-UP A DIALOG WINDOW!
|
||||
Plugins that create external windows are incredibly annoying for users, and
|
||||
cause all sorts of headaches for hosts. Don't be the person who writes that
|
||||
plugin that irritates everyone with a nagging dialog box every time they scan!
|
||||
*/
|
||||
jassert (JUCEApplicationBase::isStandaloneApp() || findParentComponentOfClass<DialogWindow>() == nullptr);
|
||||
|
||||
const int buttonHeight = 22;
|
||||
|
||||
auto r = getLocalBounds().reduced (10, 20);
|
||||
|
||||
auto buttonArea = r.removeFromBottom (buttonHeight);
|
||||
registerButton.changeWidthToFitText (buttonHeight);
|
||||
cancelButton.changeWidthToFitText (buttonHeight);
|
||||
|
||||
const int gap = 20;
|
||||
buttonArea = buttonArea.withSizeKeepingCentre (registerButton.getWidth()
|
||||
+ (cancelButton.isVisible() ? gap + cancelButton.getWidth() : 0),
|
||||
buttonHeight);
|
||||
registerButton.setBounds (buttonArea.removeFromLeft (registerButton.getWidth()));
|
||||
buttonArea.removeFromLeft (gap);
|
||||
cancelButton.setBounds (buttonArea);
|
||||
|
||||
r.removeFromBottom (20);
|
||||
|
||||
// (force use of a default system font to make sure it has the password blob character)
|
||||
Font font (Font::getDefaultTypefaceForFont (Font (Font::getDefaultSansSerifFontName(),
|
||||
Font::getDefaultStyle(),
|
||||
5.0f)));
|
||||
|
||||
const int boxHeight = 24;
|
||||
passwordBox.setBounds (r.removeFromBottom (boxHeight));
|
||||
passwordBox.setInputRestrictions (64);
|
||||
passwordBox.setFont (font);
|
||||
|
||||
r.removeFromBottom (20);
|
||||
emailBox.setBounds (r.removeFromBottom (boxHeight));
|
||||
emailBox.setInputRestrictions (512);
|
||||
emailBox.setFont (font);
|
||||
|
||||
r.removeFromBottom (20);
|
||||
|
||||
message.setBounds (r);
|
||||
|
||||
if (unlockingOverlay != nullptr)
|
||||
unlockingOverlay->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::lookAndFeelChanged()
|
||||
{
|
||||
Colour labelCol (findColour (TextEditor::backgroundColourId).contrasting (0.5f));
|
||||
|
||||
emailBox.setTextToShowWhenEmpty (TRANS("Email Address"), labelCol);
|
||||
passwordBox.setTextToShowWhenEmpty (TRANS("Password"), labelCol);
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::showBubbleMessage (const String& text, Component& target)
|
||||
{
|
||||
bubble.reset (new BubbleMessageComponent (500));
|
||||
addChildComponent (bubble.get());
|
||||
|
||||
AttributedString attString;
|
||||
attString.append (text, Font (16.0f));
|
||||
|
||||
bubble->showAt (getLocalArea (&target, target.getLocalBounds()),
|
||||
attString, 500, // numMillisecondsBeforeRemoving
|
||||
true, // removeWhenMouseClicked
|
||||
false); // deleteSelfAfterUse
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::buttonClicked (Button* b)
|
||||
{
|
||||
if (b == ®isterButton)
|
||||
attemptRegistration();
|
||||
else if (b == &cancelButton)
|
||||
dismiss();
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::attemptRegistration()
|
||||
{
|
||||
if (unlockingOverlay == nullptr)
|
||||
{
|
||||
if (emailBox.getText().trim().length() < 3)
|
||||
{
|
||||
showBubbleMessage (TRANS ("Please enter a valid email address!"), emailBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordBox.getText().trim().length() < 3)
|
||||
{
|
||||
showBubbleMessage (TRANS ("Please enter a valid password!"), passwordBox);
|
||||
return;
|
||||
}
|
||||
|
||||
status.setUserEmail (emailBox.getText());
|
||||
|
||||
addAndMakeVisible (unlockingOverlay = new OverlayComp (*this, showOverlayCancelButton));
|
||||
resized();
|
||||
unlockingOverlay->enterModalState();
|
||||
}
|
||||
}
|
||||
|
||||
void OnlineUnlockForm::dismiss()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,99 +1,99 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/** Acts as a GUI which asks the user for their details, and calls the appropriate
|
||||
methods on your OnlineUnlockStatus object to attempt to register the app.
|
||||
|
||||
You should create one of these components and add it to your parent window,
|
||||
or use a DialogWindow to display it as a pop-up. But if you're writing a plugin,
|
||||
then DO NOT USE A DIALOG WINDOW! Add it as a child component of your plugin's editor
|
||||
component instead. Plugins that pop up external registration windows are incredibly
|
||||
annoying, and cause all sorts of headaches for hosts. Don't be the person who
|
||||
writes that plugin that irritates everyone with a dialog box every time they
|
||||
try to scan for new plugins!
|
||||
|
||||
Note that after adding it, you should put the component into a modal state,
|
||||
and it will automatically delete itself when it has completed.
|
||||
|
||||
Although it deletes itself, it's also OK to delete it manually yourself
|
||||
if you need to get rid of it sooner.
|
||||
|
||||
@see OnlineUnlockStatus
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API OnlineUnlockForm : public Component,
|
||||
private Button::Listener
|
||||
{
|
||||
public:
|
||||
/** Creates an unlock form that will work with the given status object.
|
||||
The userInstructions will be displayed above the email and password boxes.
|
||||
*/
|
||||
OnlineUnlockForm (OnlineUnlockStatus&,
|
||||
const String& userInstructions,
|
||||
bool hasCancelButton = true,
|
||||
bool overlayHasCancelButton = false);
|
||||
|
||||
/** Destructor. */
|
||||
~OnlineUnlockForm() override;
|
||||
|
||||
/** This is called when the form is dismissed (either cancelled or when registration
|
||||
succeeds).
|
||||
By default it will delete this, but you can override it to do other things.
|
||||
*/
|
||||
virtual void dismiss();
|
||||
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
|
||||
Label message;
|
||||
TextEditor emailBox, passwordBox;
|
||||
TextButton registerButton, cancelButton;
|
||||
|
||||
private:
|
||||
OnlineUnlockStatus& status;
|
||||
std::unique_ptr<BubbleMessageComponent> bubble;
|
||||
|
||||
bool showOverlayCancelButton;
|
||||
|
||||
struct OverlayComp;
|
||||
friend struct OverlayComp;
|
||||
Component::SafePointer<Component> unlockingOverlay;
|
||||
|
||||
void buttonClicked (Button*) override;
|
||||
void attemptRegistration();
|
||||
void showBubbleMessage (const String&, Component&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnlineUnlockForm)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/** Acts as a GUI which asks the user for their details, and calls the appropriate
|
||||
methods on your OnlineUnlockStatus object to attempt to register the app.
|
||||
|
||||
You should create one of these components and add it to your parent window,
|
||||
or use a DialogWindow to display it as a pop-up. But if you're writing a plugin,
|
||||
then DO NOT USE A DIALOG WINDOW! Add it as a child component of your plugin's editor
|
||||
component instead. Plugins that pop up external registration windows are incredibly
|
||||
annoying, and cause all sorts of headaches for hosts. Don't be the person who
|
||||
writes that plugin that irritates everyone with a dialog box every time they
|
||||
try to scan for new plugins!
|
||||
|
||||
Note that after adding it, you should put the component into a modal state,
|
||||
and it will automatically delete itself when it has completed.
|
||||
|
||||
Although it deletes itself, it's also OK to delete it manually yourself
|
||||
if you need to get rid of it sooner.
|
||||
|
||||
@see OnlineUnlockStatus
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API OnlineUnlockForm : public Component,
|
||||
private Button::Listener
|
||||
{
|
||||
public:
|
||||
/** Creates an unlock form that will work with the given status object.
|
||||
The userInstructions will be displayed above the email and password boxes.
|
||||
*/
|
||||
OnlineUnlockForm (OnlineUnlockStatus&,
|
||||
const String& userInstructions,
|
||||
bool hasCancelButton = true,
|
||||
bool overlayHasCancelButton = false);
|
||||
|
||||
/** Destructor. */
|
||||
~OnlineUnlockForm() override;
|
||||
|
||||
/** This is called when the form is dismissed (either cancelled or when registration
|
||||
succeeds).
|
||||
By default it will delete this, but you can override it to do other things.
|
||||
*/
|
||||
virtual void dismiss();
|
||||
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
|
||||
Label message;
|
||||
TextEditor emailBox, passwordBox;
|
||||
TextButton registerButton, cancelButton;
|
||||
|
||||
private:
|
||||
OnlineUnlockStatus& status;
|
||||
std::unique_ptr<BubbleMessageComponent> bubble;
|
||||
|
||||
bool showOverlayCancelButton;
|
||||
|
||||
struct OverlayComp;
|
||||
friend struct OverlayComp;
|
||||
Component::SafePointer<Component> unlockingOverlay;
|
||||
|
||||
void buttonClicked (Button*) override;
|
||||
void attemptRegistration();
|
||||
void showBubbleMessage (const String&, Component&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnlineUnlockForm)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,277 +1,277 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
A base class for online unlocking systems.
|
||||
|
||||
This class stores information about whether your app has been unlocked for the
|
||||
current machine, and handles communication with a web-store to perform the
|
||||
unlock procedure.
|
||||
|
||||
You probably won't ever use this base class directly, but rather a store-specific
|
||||
subclass such as TracktionMarketplaceStatus, which knows how to talk to the particular
|
||||
online store that you're using.
|
||||
|
||||
To use it, you create a subclass which implements all the pure virtual methods
|
||||
(see their comments to find out what you'll need to make them do).
|
||||
|
||||
Then you can create an instance of your subclass which will hold the registration
|
||||
state. Typically, you'll want to just keep a single instance of the class around for
|
||||
the duration of your app. You can then call its methods to handle the various
|
||||
registration tasks.
|
||||
|
||||
Areas of your code that need to know whether the user is registered (e.g. to decide
|
||||
whether a particular feature is available) should call isUnlocked() to find out.
|
||||
|
||||
If you want to create a GUI that allows your users to enter their details and
|
||||
register, see the OnlineUnlockForm class.
|
||||
|
||||
@see OnlineUnlockForm, KeyGeneration
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API OnlineUnlockStatus
|
||||
{
|
||||
public:
|
||||
OnlineUnlockStatus();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~OnlineUnlockStatus();
|
||||
|
||||
//==============================================================================
|
||||
/** This must return your product's ID, as allocated by the store. */
|
||||
virtual String getProductID() = 0;
|
||||
|
||||
/** This must check whether a product ID string that the server returned is OK for
|
||||
unlocking the current app.
|
||||
*/
|
||||
virtual bool doesProductIDMatch (const String& returnedIDFromServer) = 0;
|
||||
|
||||
/** This must return the RSA public key for authenticating responses from
|
||||
the server for this app. You can get this key from your marketplace
|
||||
account page.
|
||||
*/
|
||||
virtual RSAKey getPublicKey() = 0;
|
||||
|
||||
/** This method must store the given string somewhere in your app's
|
||||
persistent properties, so it can be retrieved later by getState().
|
||||
*/
|
||||
virtual void saveState (const String&) = 0;
|
||||
|
||||
/** This method must retrieve the last state that was provided by the
|
||||
saveState method.
|
||||
|
||||
On first-run, it should just return an empty string.
|
||||
*/
|
||||
virtual String getState() = 0;
|
||||
|
||||
/** Returns the name of the web-store website, not for communication, but for
|
||||
presenting to the user.
|
||||
*/
|
||||
virtual String getWebsiteName() = 0;
|
||||
|
||||
/** Returns the URL of the authentication API. */
|
||||
virtual URL getServerAuthenticationURL() = 0;
|
||||
|
||||
/** Subclasses that talk to a particular web-store will implement this method
|
||||
to contact their webserver and attempt to unlock the current machine for
|
||||
the given username and password. The return value is the XML text from the
|
||||
server which contains error information and/or the encrypted keyfile.
|
||||
*/
|
||||
virtual String readReplyFromWebserver (const String& email, const String& password) = 0;
|
||||
|
||||
/** Returns a list of strings, any of which should be unique to this
|
||||
physical computer.
|
||||
|
||||
When testing whether the user is allowed to use the product on this
|
||||
machine, this list of tokens is compared to the ones that were stored
|
||||
on the webserver.
|
||||
|
||||
The default implementation of this method will simply call
|
||||
MachineIDUtilities::getLocalMachineIDs(), which provides a default
|
||||
version of this functionality.
|
||||
*/
|
||||
virtual StringArray getLocalMachineIDs();
|
||||
|
||||
/** This method will be called if the user cancels the connection to the webserver
|
||||
by clicking the cancel button in OnlineUnlockForm::OverlayComp.
|
||||
|
||||
The default implementation of this method does nothing but you should use it to
|
||||
cancel any WebInputStreams that may be connecting.
|
||||
*/
|
||||
virtual void userCancelled();
|
||||
|
||||
virtual String getMessageForConnectionFailure (bool isInternetConnectionWorking);
|
||||
virtual String getMessageForUnexpectedReply();
|
||||
|
||||
//==============================================================================
|
||||
// The following methods can be called by your app:
|
||||
|
||||
/** Returns true if the product has been successfully authorised for this machine.
|
||||
|
||||
The reason it returns a variant rather than a bool is just to make it marginally
|
||||
more tedious for crackers to work around. Hopefully if this method gets inlined
|
||||
they'll need to hack all the places where you call it, rather than just the
|
||||
function itself.
|
||||
|
||||
Bear in mind that each place where you check this return value will need to be
|
||||
changed by a cracker in order to unlock your app, so the more places you call this
|
||||
method, the more hassle it will be for them to find and crack them all.
|
||||
*/
|
||||
inline var isUnlocked() const { return status[unlockedProp]; }
|
||||
|
||||
/** Returns the Time when the keyfile expires.
|
||||
|
||||
If a the key file obtained has an expiry time, isUnlocked will return false and this
|
||||
will return a non-zero time. The interpretation of this is up to your app but could
|
||||
be used for subscription based models or trial periods.
|
||||
*/
|
||||
inline Time getExpiryTime() const { return Time (static_cast<int64> (status[expiryTimeProp])); }
|
||||
|
||||
/** Optionally allows the app to provide the user's email address if
|
||||
it is known.
|
||||
You don't need to call this, but if you do it may save the user
|
||||
typing it in.
|
||||
*/
|
||||
void setUserEmail (const String& usernameOrEmail);
|
||||
|
||||
/** Returns the user's email address if known. */
|
||||
String getUserEmail() const;
|
||||
|
||||
/** Attempts to perform an unlock using a block of key-file data provided.
|
||||
You may wish to use this as a way of allowing a user to unlock your app
|
||||
by drag-and-dropping a file containing the key data, or by letting them
|
||||
select such a file. This is often needed for allowing registration on
|
||||
machines without internet access.
|
||||
*/
|
||||
bool applyKeyFile (String keyFileContent);
|
||||
|
||||
/** This provides some details about the reply that the server gave in a call
|
||||
to attemptWebserverUnlock().
|
||||
*/
|
||||
struct UnlockResult
|
||||
{
|
||||
/** If an unlock operation fails, this is the error message that the webserver
|
||||
supplied (or a message saying that the server couldn't be contacted)
|
||||
*/
|
||||
String errorMessage;
|
||||
|
||||
/** This is a message that the webserver returned, and which the user should
|
||||
be shown.
|
||||
|
||||
It's not necessarily an error message, e.g. it might say that there's a
|
||||
new version of the app available or some other status update.
|
||||
*/
|
||||
String informativeMessage;
|
||||
|
||||
/** If the webserver wants the user to be directed to a web-page for further
|
||||
information, this is the URL that it would like them to go to.
|
||||
*/
|
||||
String urlToLaunch;
|
||||
|
||||
/** If the unlock operation succeeded, this will be set to true. */
|
||||
bool succeeded;
|
||||
};
|
||||
|
||||
/** Contacts the webserver and attempts to perform a registration with the
|
||||
given user details.
|
||||
|
||||
The return value will either be a success, or a failure with an error message
|
||||
from the server, so you should show this message to your user.
|
||||
|
||||
Because this method blocks while it contacts the server, you must run it on
|
||||
a background thread, not on the message thread. For an easier way to create
|
||||
a GUI to do the unlocking, see OnlineUnlockForm.
|
||||
*/
|
||||
UnlockResult attemptWebserverUnlock (const String& email, const String& password);
|
||||
|
||||
/** Attempts to load the status from the state retrieved by getState().
|
||||
Call this somewhere in your app's startup code.
|
||||
*/
|
||||
void load();
|
||||
|
||||
/** Triggers a call to saveState which you can use to store the current unlock status
|
||||
in your app's settings.
|
||||
*/
|
||||
void save();
|
||||
|
||||
/** This class contains some utility functions that might help with machine ID generation. */
|
||||
struct MachineIDUtilities
|
||||
{
|
||||
/** Returns a character that represents the current OS.
|
||||
E.g. 'M' for Mac, 'W' for windows, etc
|
||||
*/
|
||||
static char getPlatformPrefix();
|
||||
|
||||
/** Returns an encoded hash string from the given input string, prefixing it with
|
||||
a letter to represent the current OS type.
|
||||
*/
|
||||
static String getEncodedIDString (const String& inputString);
|
||||
|
||||
/** Utility function that you may want to use in your machine-ID generation code.
|
||||
This adds an ID string to the given array which is a hash of the filesystem ID of the
|
||||
given file.
|
||||
*/
|
||||
static bool addFileIDToList (StringArray& result, const File& file);
|
||||
|
||||
/** Utility function that you may want to use in your machine-ID generation code.
|
||||
This adds some ID strings to the given array which represent each MAC address of the machine.
|
||||
*/
|
||||
static void addMACAddressesToList (StringArray& result);
|
||||
|
||||
/** This method calculates some machine IDs based on things like network
|
||||
MAC addresses, hard-disk IDs, etc, but if you want, you can overload
|
||||
it to generate your own list of IDs.
|
||||
|
||||
The IDs that are returned should be short alphanumeric strings
|
||||
without any punctuation characters. Since users may need to type
|
||||
them, case is ignored when comparing them.
|
||||
|
||||
Note that the first item in the list is considered to be the
|
||||
"main" ID, and this will be the one that is displayed to the user
|
||||
and registered with the marketplace webserver. Subsequent IDs are
|
||||
just used as fallback to avoid false negatives when checking for
|
||||
registration on machines which have had hardware added/removed
|
||||
since the product was first registered.
|
||||
*/
|
||||
static StringArray getLocalMachineIDs();
|
||||
};
|
||||
|
||||
private:
|
||||
ValueTree status;
|
||||
|
||||
UnlockResult handleXmlReply (XmlElement);
|
||||
UnlockResult handleFailedConnection();
|
||||
|
||||
static const char* unlockedProp;
|
||||
static const char* expiryTimeProp;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnlineUnlockStatus)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
A base class for online unlocking systems.
|
||||
|
||||
This class stores information about whether your app has been unlocked for the
|
||||
current machine, and handles communication with a web-store to perform the
|
||||
unlock procedure.
|
||||
|
||||
You probably won't ever use this base class directly, but rather a store-specific
|
||||
subclass such as TracktionMarketplaceStatus, which knows how to talk to the particular
|
||||
online store that you're using.
|
||||
|
||||
To use it, you create a subclass which implements all the pure virtual methods
|
||||
(see their comments to find out what you'll need to make them do).
|
||||
|
||||
Then you can create an instance of your subclass which will hold the registration
|
||||
state. Typically, you'll want to just keep a single instance of the class around for
|
||||
the duration of your app. You can then call its methods to handle the various
|
||||
registration tasks.
|
||||
|
||||
Areas of your code that need to know whether the user is registered (e.g. to decide
|
||||
whether a particular feature is available) should call isUnlocked() to find out.
|
||||
|
||||
If you want to create a GUI that allows your users to enter their details and
|
||||
register, see the OnlineUnlockForm class.
|
||||
|
||||
@see OnlineUnlockForm, KeyGeneration
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API OnlineUnlockStatus
|
||||
{
|
||||
public:
|
||||
OnlineUnlockStatus();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~OnlineUnlockStatus();
|
||||
|
||||
//==============================================================================
|
||||
/** This must return your product's ID, as allocated by the store. */
|
||||
virtual String getProductID() = 0;
|
||||
|
||||
/** This must check whether a product ID string that the server returned is OK for
|
||||
unlocking the current app.
|
||||
*/
|
||||
virtual bool doesProductIDMatch (const String& returnedIDFromServer) = 0;
|
||||
|
||||
/** This must return the RSA public key for authenticating responses from
|
||||
the server for this app. You can get this key from your marketplace
|
||||
account page.
|
||||
*/
|
||||
virtual RSAKey getPublicKey() = 0;
|
||||
|
||||
/** This method must store the given string somewhere in your app's
|
||||
persistent properties, so it can be retrieved later by getState().
|
||||
*/
|
||||
virtual void saveState (const String&) = 0;
|
||||
|
||||
/** This method must retrieve the last state that was provided by the
|
||||
saveState method.
|
||||
|
||||
On first-run, it should just return an empty string.
|
||||
*/
|
||||
virtual String getState() = 0;
|
||||
|
||||
/** Returns the name of the web-store website, not for communication, but for
|
||||
presenting to the user.
|
||||
*/
|
||||
virtual String getWebsiteName() = 0;
|
||||
|
||||
/** Returns the URL of the authentication API. */
|
||||
virtual URL getServerAuthenticationURL() = 0;
|
||||
|
||||
/** Subclasses that talk to a particular web-store will implement this method
|
||||
to contact their webserver and attempt to unlock the current machine for
|
||||
the given username and password. The return value is the XML text from the
|
||||
server which contains error information and/or the encrypted keyfile.
|
||||
*/
|
||||
virtual String readReplyFromWebserver (const String& email, const String& password) = 0;
|
||||
|
||||
/** Returns a list of strings, any of which should be unique to this
|
||||
physical computer.
|
||||
|
||||
When testing whether the user is allowed to use the product on this
|
||||
machine, this list of tokens is compared to the ones that were stored
|
||||
on the webserver.
|
||||
|
||||
The default implementation of this method will simply call
|
||||
MachineIDUtilities::getLocalMachineIDs(), which provides a default
|
||||
version of this functionality.
|
||||
*/
|
||||
virtual StringArray getLocalMachineIDs();
|
||||
|
||||
/** This method will be called if the user cancels the connection to the webserver
|
||||
by clicking the cancel button in OnlineUnlockForm::OverlayComp.
|
||||
|
||||
The default implementation of this method does nothing but you should use it to
|
||||
cancel any WebInputStreams that may be connecting.
|
||||
*/
|
||||
virtual void userCancelled();
|
||||
|
||||
virtual String getMessageForConnectionFailure (bool isInternetConnectionWorking);
|
||||
virtual String getMessageForUnexpectedReply();
|
||||
|
||||
//==============================================================================
|
||||
// The following methods can be called by your app:
|
||||
|
||||
/** Returns true if the product has been successfully authorised for this machine.
|
||||
|
||||
The reason it returns a variant rather than a bool is just to make it marginally
|
||||
more tedious for crackers to work around. Hopefully if this method gets inlined
|
||||
they'll need to hack all the places where you call it, rather than just the
|
||||
function itself.
|
||||
|
||||
Bear in mind that each place where you check this return value will need to be
|
||||
changed by a cracker in order to unlock your app, so the more places you call this
|
||||
method, the more hassle it will be for them to find and crack them all.
|
||||
*/
|
||||
inline var isUnlocked() const { return status[unlockedProp]; }
|
||||
|
||||
/** Returns the Time when the keyfile expires.
|
||||
|
||||
If a the key file obtained has an expiry time, isUnlocked will return false and this
|
||||
will return a non-zero time. The interpretation of this is up to your app but could
|
||||
be used for subscription based models or trial periods.
|
||||
*/
|
||||
inline Time getExpiryTime() const { return Time (static_cast<int64> (status[expiryTimeProp])); }
|
||||
|
||||
/** Optionally allows the app to provide the user's email address if
|
||||
it is known.
|
||||
You don't need to call this, but if you do it may save the user
|
||||
typing it in.
|
||||
*/
|
||||
void setUserEmail (const String& usernameOrEmail);
|
||||
|
||||
/** Returns the user's email address if known. */
|
||||
String getUserEmail() const;
|
||||
|
||||
/** Attempts to perform an unlock using a block of key-file data provided.
|
||||
You may wish to use this as a way of allowing a user to unlock your app
|
||||
by drag-and-dropping a file containing the key data, or by letting them
|
||||
select such a file. This is often needed for allowing registration on
|
||||
machines without internet access.
|
||||
*/
|
||||
bool applyKeyFile (String keyFileContent);
|
||||
|
||||
/** This provides some details about the reply that the server gave in a call
|
||||
to attemptWebserverUnlock().
|
||||
*/
|
||||
struct UnlockResult
|
||||
{
|
||||
/** If an unlock operation fails, this is the error message that the webserver
|
||||
supplied (or a message saying that the server couldn't be contacted)
|
||||
*/
|
||||
String errorMessage;
|
||||
|
||||
/** This is a message that the webserver returned, and which the user should
|
||||
be shown.
|
||||
|
||||
It's not necessarily an error message, e.g. it might say that there's a
|
||||
new version of the app available or some other status update.
|
||||
*/
|
||||
String informativeMessage;
|
||||
|
||||
/** If the webserver wants the user to be directed to a web-page for further
|
||||
information, this is the URL that it would like them to go to.
|
||||
*/
|
||||
String urlToLaunch;
|
||||
|
||||
/** If the unlock operation succeeded, this will be set to true. */
|
||||
bool succeeded;
|
||||
};
|
||||
|
||||
/** Contacts the webserver and attempts to perform a registration with the
|
||||
given user details.
|
||||
|
||||
The return value will either be a success, or a failure with an error message
|
||||
from the server, so you should show this message to your user.
|
||||
|
||||
Because this method blocks while it contacts the server, you must run it on
|
||||
a background thread, not on the message thread. For an easier way to create
|
||||
a GUI to do the unlocking, see OnlineUnlockForm.
|
||||
*/
|
||||
UnlockResult attemptWebserverUnlock (const String& email, const String& password);
|
||||
|
||||
/** Attempts to load the status from the state retrieved by getState().
|
||||
Call this somewhere in your app's startup code.
|
||||
*/
|
||||
void load();
|
||||
|
||||
/** Triggers a call to saveState which you can use to store the current unlock status
|
||||
in your app's settings.
|
||||
*/
|
||||
void save();
|
||||
|
||||
/** This class contains some utility functions that might help with machine ID generation. */
|
||||
struct MachineIDUtilities
|
||||
{
|
||||
/** Returns a character that represents the current OS.
|
||||
E.g. 'M' for Mac, 'W' for windows, etc
|
||||
*/
|
||||
static char getPlatformPrefix();
|
||||
|
||||
/** Returns an encoded hash string from the given input string, prefixing it with
|
||||
a letter to represent the current OS type.
|
||||
*/
|
||||
static String getEncodedIDString (const String& inputString);
|
||||
|
||||
/** Utility function that you may want to use in your machine-ID generation code.
|
||||
This adds an ID string to the given array which is a hash of the filesystem ID of the
|
||||
given file.
|
||||
*/
|
||||
static bool addFileIDToList (StringArray& result, const File& file);
|
||||
|
||||
/** Utility function that you may want to use in your machine-ID generation code.
|
||||
This adds some ID strings to the given array which represent each MAC address of the machine.
|
||||
*/
|
||||
static void addMACAddressesToList (StringArray& result);
|
||||
|
||||
/** This method calculates some machine IDs based on things like network
|
||||
MAC addresses, hard-disk IDs, etc, but if you want, you can overload
|
||||
it to generate your own list of IDs.
|
||||
|
||||
The IDs that are returned should be short alphanumeric strings
|
||||
without any punctuation characters. Since users may need to type
|
||||
them, case is ignored when comparing them.
|
||||
|
||||
Note that the first item in the list is considered to be the
|
||||
"main" ID, and this will be the one that is displayed to the user
|
||||
and registered with the marketplace webserver. Subsequent IDs are
|
||||
just used as fallback to avoid false negatives when checking for
|
||||
registration on machines which have had hardware added/removed
|
||||
since the product was first registered.
|
||||
*/
|
||||
static StringArray getLocalMachineIDs();
|
||||
};
|
||||
|
||||
private:
|
||||
ValueTree status;
|
||||
|
||||
UnlockResult handleXmlReply (XmlElement);
|
||||
UnlockResult handleFailedConnection();
|
||||
|
||||
static const char* unlockedProp;
|
||||
static const char* expiryTimeProp;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnlineUnlockStatus)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,90 +1,90 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
TracktionMarketplaceStatus::TracktionMarketplaceStatus() {}
|
||||
|
||||
URL TracktionMarketplaceStatus::getServerAuthenticationURL()
|
||||
{
|
||||
return URL ("https://www.tracktion.com/marketplace/authenticate.php");
|
||||
}
|
||||
|
||||
String TracktionMarketplaceStatus::getWebsiteName()
|
||||
{
|
||||
return "tracktion.com";
|
||||
}
|
||||
|
||||
bool TracktionMarketplaceStatus::doesProductIDMatch (const String& returnedIDFromServer)
|
||||
{
|
||||
return getProductID() == returnedIDFromServer;
|
||||
}
|
||||
|
||||
String TracktionMarketplaceStatus::readReplyFromWebserver (const String& email, const String& password)
|
||||
{
|
||||
URL url (getServerAuthenticationURL()
|
||||
.withParameter ("product", getProductID())
|
||||
.withParameter ("email", email)
|
||||
.withParameter ("pw", password)
|
||||
.withParameter ("os", SystemStats::getOperatingSystemName())
|
||||
.withParameter ("mach", getLocalMachineIDs()[0]));
|
||||
|
||||
DBG ("Trying to unlock via URL: " << url.toString (true));
|
||||
|
||||
{
|
||||
ScopedLock lock (streamCreationLock);
|
||||
stream.reset (new WebInputStream (url, true));
|
||||
}
|
||||
|
||||
if (stream->connect (nullptr))
|
||||
{
|
||||
auto thread = Thread::getCurrentThread();
|
||||
MemoryOutputStream result;
|
||||
|
||||
while (! (stream->isExhausted() || stream->isError()
|
||||
|| (thread != nullptr && thread->threadShouldExit())))
|
||||
{
|
||||
auto bytesRead = result.writeFromInputStream (*stream, 8192);
|
||||
|
||||
if (bytesRead < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void TracktionMarketplaceStatus::userCancelled()
|
||||
{
|
||||
ScopedLock lock (streamCreationLock);
|
||||
|
||||
if (stream != nullptr)
|
||||
stream->cancel();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
TracktionMarketplaceStatus::TracktionMarketplaceStatus() {}
|
||||
|
||||
URL TracktionMarketplaceStatus::getServerAuthenticationURL()
|
||||
{
|
||||
return URL ("https://www.tracktion.com/marketplace/authenticate.php");
|
||||
}
|
||||
|
||||
String TracktionMarketplaceStatus::getWebsiteName()
|
||||
{
|
||||
return "tracktion.com";
|
||||
}
|
||||
|
||||
bool TracktionMarketplaceStatus::doesProductIDMatch (const String& returnedIDFromServer)
|
||||
{
|
||||
return getProductID() == returnedIDFromServer;
|
||||
}
|
||||
|
||||
String TracktionMarketplaceStatus::readReplyFromWebserver (const String& email, const String& password)
|
||||
{
|
||||
URL url (getServerAuthenticationURL()
|
||||
.withParameter ("product", getProductID())
|
||||
.withParameter ("email", email)
|
||||
.withParameter ("pw", password)
|
||||
.withParameter ("os", SystemStats::getOperatingSystemName())
|
||||
.withParameter ("mach", getLocalMachineIDs()[0]));
|
||||
|
||||
DBG ("Trying to unlock via URL: " << url.toString (true));
|
||||
|
||||
{
|
||||
ScopedLock lock (streamCreationLock);
|
||||
stream.reset (new WebInputStream (url, true));
|
||||
}
|
||||
|
||||
if (stream->connect (nullptr))
|
||||
{
|
||||
auto thread = Thread::getCurrentThread();
|
||||
MemoryOutputStream result;
|
||||
|
||||
while (! (stream->isExhausted() || stream->isError()
|
||||
|| (thread != nullptr && thread->threadShouldExit())))
|
||||
{
|
||||
auto bytesRead = result.writeFromInputStream (*stream, 8192);
|
||||
|
||||
if (bytesRead < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void TracktionMarketplaceStatus::userCancelled()
|
||||
{
|
||||
ScopedLock lock (streamCreationLock);
|
||||
|
||||
if (stream != nullptr)
|
||||
stream->cancel();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,64 +1,64 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
An implementation of the OnlineUnlockStatus class which talks to the
|
||||
Tracktion Marketplace server.
|
||||
|
||||
For details about how to use this class, see the docs for the base
|
||||
class: OnlineUnlockStatus. Basically, you need to inherit from it, and
|
||||
implement all the pure virtual methods to tell it about your product.
|
||||
|
||||
@see OnlineUnlockStatus, OnlineUnlockForm, KeyGeneration
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API TracktionMarketplaceStatus : public OnlineUnlockStatus
|
||||
{
|
||||
public:
|
||||
TracktionMarketplaceStatus();
|
||||
|
||||
/** @internal */
|
||||
bool doesProductIDMatch (const String& returnedIDFromServer) override;
|
||||
/** @internal */
|
||||
URL getServerAuthenticationURL() override;
|
||||
/** @internal */
|
||||
String getWebsiteName() override;
|
||||
/** @internal */
|
||||
String readReplyFromWebserver (const String& email, const String& password) override;
|
||||
/** @internal */
|
||||
void userCancelled() override;
|
||||
|
||||
private:
|
||||
CriticalSection streamCreationLock;
|
||||
std::unique_ptr<WebInputStream> stream;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracktionMarketplaceStatus)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
An implementation of the OnlineUnlockStatus class which talks to the
|
||||
Tracktion Marketplace server.
|
||||
|
||||
For details about how to use this class, see the docs for the base
|
||||
class: OnlineUnlockStatus. Basically, you need to inherit from it, and
|
||||
implement all the pure virtual methods to tell it about your product.
|
||||
|
||||
@see OnlineUnlockStatus, OnlineUnlockForm, KeyGeneration
|
||||
|
||||
@tags{ProductUnlocking}
|
||||
*/
|
||||
class JUCE_API TracktionMarketplaceStatus : public OnlineUnlockStatus
|
||||
{
|
||||
public:
|
||||
TracktionMarketplaceStatus();
|
||||
|
||||
/** @internal */
|
||||
bool doesProductIDMatch (const String& returnedIDFromServer) override;
|
||||
/** @internal */
|
||||
URL getServerAuthenticationURL() override;
|
||||
/** @internal */
|
||||
String getWebsiteName() override;
|
||||
/** @internal */
|
||||
String readReplyFromWebserver (const String& email, const String& password) override;
|
||||
/** @internal */
|
||||
void userCancelled() override;
|
||||
|
||||
private:
|
||||
CriticalSection streamCreationLock;
|
||||
std::unique_ptr<WebInputStream> stream;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracktionMarketplaceStatus)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,215 +1,226 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import com.android.billingclient.api.*;
|
||||
|
||||
public class JuceBillingClient implements PurchasesUpdatedListener, BillingClientStateListener {
|
||||
private native void skuDetailsQueryCallback(long host, java.util.List<SkuDetails> skuDetails);
|
||||
private native void purchasesListQueryCallback(long host, java.util.List<Purchase> purchases);
|
||||
private native void purchaseCompletedCallback(long host, Purchase purchase, int responseCode);
|
||||
private native void purchaseConsumedCallback(long host, String productIdentifier, int responseCode);
|
||||
|
||||
public JuceBillingClient(android.content.Context context, long hostToUse) {
|
||||
host = hostToUse;
|
||||
|
||||
billingClient = BillingClient.newBuilder(context)
|
||||
.enablePendingPurchases()
|
||||
.setListener(this)
|
||||
.build();
|
||||
|
||||
billingClient.startConnection(this);
|
||||
}
|
||||
|
||||
public void endConnection() {
|
||||
billingClient.endConnection();
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return billingClient.isReady();
|
||||
}
|
||||
|
||||
public boolean isBillingSupported() {
|
||||
return billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS).getResponseCode()
|
||||
== BillingClient.BillingResponseCode.OK;
|
||||
}
|
||||
|
||||
public void querySkuDetails(final String[] skusToQuery) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final java.util.List<String> skuList = java.util.Arrays.asList(skusToQuery);
|
||||
|
||||
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(skuList)
|
||||
.setType(BillingClient.SkuType.INAPP);
|
||||
|
||||
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
|
||||
@Override
|
||||
public void onSkuDetailsResponse(BillingResult billingResult, final java.util.List<SkuDetails> inAppSkuDetails) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(skuList)
|
||||
.setType(BillingClient.SkuType.SUBS);
|
||||
|
||||
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
|
||||
@Override
|
||||
public void onSkuDetailsResponse(BillingResult billingResult, java.util.List<SkuDetails> subsSkuDetails) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
subsSkuDetails.addAll(inAppSkuDetails);
|
||||
skuDetailsQueryCallback(host, subsSkuDetails);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void launchBillingFlow(final android.app.Activity activity, final BillingFlowParams params) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BillingResult r = billingClient.launchBillingFlow(activity, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void queryPurchases() {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Purchase.PurchasesResult inAppPurchases = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
|
||||
Purchase.PurchasesResult subsPurchases = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
|
||||
|
||||
if (inAppPurchases.getResponseCode() == BillingClient.BillingResponseCode.OK
|
||||
&& subsPurchases.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
java.util.List<Purchase> purchaseList = inAppPurchases.getPurchasesList();
|
||||
purchaseList.addAll(subsPurchases.getPurchasesList());
|
||||
|
||||
purchasesListQueryCallback(host, purchaseList);
|
||||
return;
|
||||
}
|
||||
|
||||
purchasesListQueryCallback(host, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void consumePurchase(final String productIdentifier, final String purchaseToken) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ConsumeParams consumeParams = ConsumeParams.newBuilder()
|
||||
.setPurchaseToken(purchaseToken)
|
||||
.build();
|
||||
|
||||
billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
|
||||
@Override
|
||||
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
|
||||
purchaseConsumedCallback(host, productIdentifier, billingResult.getResponseCode());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchasesUpdated(BillingResult result, java.util.List<Purchase> purchases) {
|
||||
int responseCode = result.getResponseCode();
|
||||
|
||||
if (purchases != null) {
|
||||
for (Purchase purchase : purchases) {
|
||||
handlePurchase(purchase, responseCode);
|
||||
}
|
||||
} else {
|
||||
purchaseCompletedCallback(host, null, responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingSetupFinished(BillingResult billingResult)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void executeOnBillingClientConnection(Runnable runnable) {
|
||||
if (billingClient.isReady()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
connectAndExecute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndExecute(final Runnable executeOnSuccess) {
|
||||
billingClient.startConnection(new BillingClientStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(BillingResult billingResponse) {
|
||||
if (billingResponse.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
if (executeOnSuccess != null) {
|
||||
executeOnSuccess.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePurchase(final Purchase purchase, final int responseCode) {
|
||||
purchaseCompletedCallback(host, purchase, responseCode);
|
||||
|
||||
if (responseCode == BillingClient.BillingResponseCode.OK
|
||||
&& purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
|
||||
&& !purchase.isAcknowledged()) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();
|
||||
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
|
||||
@Override
|
||||
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private long host = 0;
|
||||
private BillingClient billingClient;
|
||||
}
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import com.android.billingclient.api.*;
|
||||
|
||||
public class JuceBillingClient implements PurchasesUpdatedListener,
|
||||
BillingClientStateListener {
|
||||
private native void productDetailsQueryCallback(long host, java.util.List<ProductDetails> productDetails);
|
||||
private native void purchasesListQueryCallback(long host, java.util.List<Purchase> purchases);
|
||||
private native void purchaseCompletedCallback(long host, Purchase purchase, int responseCode);
|
||||
private native void purchaseConsumedCallback(long host, String productIdentifier, int responseCode);
|
||||
|
||||
public JuceBillingClient(android.content.Context context, long hostToUse) {
|
||||
host = hostToUse;
|
||||
|
||||
billingClient = BillingClient.newBuilder(context)
|
||||
.enablePendingPurchases()
|
||||
.setListener(this)
|
||||
.build();
|
||||
|
||||
billingClient.startConnection(this);
|
||||
}
|
||||
|
||||
public void endConnection() {
|
||||
billingClient.endConnection();
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return billingClient.isReady();
|
||||
}
|
||||
|
||||
public boolean isBillingSupported() {
|
||||
return billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS).getResponseCode()
|
||||
== BillingClient.BillingResponseCode.OK;
|
||||
}
|
||||
|
||||
public QueryProductDetailsParams getProductListParams(final String[] productsToQuery, String type) {
|
||||
java.util.ArrayList<QueryProductDetailsParams.Product> productList = new java.util.ArrayList<>();
|
||||
|
||||
for (String product : productsToQuery)
|
||||
productList.add(QueryProductDetailsParams.Product.newBuilder().setProductId(product).setProductType(type).build());
|
||||
|
||||
return QueryProductDetailsParams.newBuilder().setProductList(productList).build();
|
||||
}
|
||||
|
||||
public void queryProductDetailsImpl(final String[] productsToQuery, java.util.List<String> productTypes, java.util.List<ProductDetails> details) {
|
||||
if (productTypes == null || productTypes.isEmpty()) {
|
||||
productDetailsQueryCallback(host, details);
|
||||
} else {
|
||||
billingClient.queryProductDetailsAsync(getProductListParams(productsToQuery, productTypes.get(0)), new ProductDetailsResponseListener() {
|
||||
@Override
|
||||
public void onProductDetailsResponse(BillingResult billingResult, java.util.List<ProductDetails> newDetails) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
details.addAll(newDetails);
|
||||
queryProductDetailsImpl(productsToQuery, productTypes.subList(1, productTypes.size()), details);
|
||||
} else {
|
||||
queryProductDetailsImpl(productsToQuery, null, details);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void queryProductDetails(final String[] productsToQuery) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String[] toCheck = {BillingClient.ProductType.INAPP, BillingClient.ProductType.SUBS};
|
||||
queryProductDetailsImpl(productsToQuery, java.util.Arrays.asList(toCheck), new java.util.ArrayList<ProductDetails>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void launchBillingFlow(final android.app.Activity activity, final BillingFlowParams params) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BillingResult r = billingClient.launchBillingFlow(activity, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void queryPurchasesImpl(java.util.List<String> toCheck, java.util.ArrayList<Purchase> purchases) {
|
||||
if (toCheck == null || toCheck.isEmpty()) {
|
||||
purchasesListQueryCallback(host, purchases);
|
||||
} else {
|
||||
billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(toCheck.get(0)).build(), new PurchasesResponseListener() {
|
||||
@Override
|
||||
public void onQueryPurchasesResponse(BillingResult billingResult, java.util.List<Purchase> list) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
purchases.addAll(list);
|
||||
queryPurchasesImpl(toCheck.subList(1, toCheck.size()), purchases);
|
||||
} else {
|
||||
queryPurchasesImpl(null, purchases);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void queryPurchases() {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String[] toCheck = {BillingClient.ProductType.INAPP, BillingClient.ProductType.SUBS};
|
||||
queryPurchasesImpl(java.util.Arrays.asList(toCheck), new java.util.ArrayList<Purchase>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void consumePurchase(final String productIdentifier, final String purchaseToken) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ConsumeParams consumeParams = ConsumeParams.newBuilder()
|
||||
.setPurchaseToken(purchaseToken)
|
||||
.build();
|
||||
|
||||
billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
|
||||
@Override
|
||||
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
|
||||
purchaseConsumedCallback(host, productIdentifier, billingResult.getResponseCode());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchasesUpdated(BillingResult result, java.util.List<Purchase> purchases) {
|
||||
int responseCode = result.getResponseCode();
|
||||
|
||||
if (purchases != null) {
|
||||
for (Purchase purchase : purchases) {
|
||||
handlePurchase(purchase, responseCode);
|
||||
}
|
||||
} else {
|
||||
purchaseCompletedCallback(host, null, responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingSetupFinished(BillingResult billingResult)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void executeOnBillingClientConnection(Runnable runnable) {
|
||||
if (billingClient.isReady()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
connectAndExecute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndExecute(final Runnable executeOnSuccess) {
|
||||
billingClient.startConnection(new BillingClientStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(BillingResult billingResponse) {
|
||||
if (billingResponse.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
if (executeOnSuccess != null) {
|
||||
executeOnSuccess.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePurchase(final Purchase purchase, final int responseCode) {
|
||||
purchaseCompletedCallback(host, purchase, responseCode);
|
||||
|
||||
if (responseCode == BillingClient.BillingResponseCode.OK
|
||||
&& purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
|
||||
&& !purchase.isAcknowledged()) {
|
||||
executeOnBillingClientConnection(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();
|
||||
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
|
||||
@Override
|
||||
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private long host = 0;
|
||||
private final BillingClient billingClient;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user