From ac6f895f64d4fa36448f18dc138fc81cc1a05969 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 16:37:05 +0900 Subject: [PATCH] Turn InAppPurchase into an EventEmitter --- atom/browser/api/atom_api_in_app_purchase.cc | 105 +++++++++++++------ atom/browser/api/atom_api_in_app_purchase.h | 40 +++---- atom/browser/mac/in_app_purchase.h | 2 +- atom/browser/mac/in_app_purchase.mm | 4 +- atom/browser/mac/in_app_purchase_observer.mm | 2 + docs/api/in-app-purchase.md | 12 ++- lib/browser/api/in-app-purchase.js | 41 ++------ spec/api-in-app-purchase-spec.js | 7 ++ 8 files changed, 123 insertions(+), 90 deletions(-) diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc index c6815c8c065c..66ab3883010f 100644 --- a/atom/browser/api/atom_api_in_app_purchase.cc +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -15,47 +15,92 @@ namespace mate { -v8::Local Converter::ToV8( - v8::Isolate* isolate, - const in_app_purchase::Payment& payment) { - mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); - dict.SetHidden("simple", true); - dict.Set("productIdentifier", payment.productIdentifier); - dict.Set("quantity", payment.quantity); - return dict.GetHandle(); -} +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const in_app_purchase::Payment& payment) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); + dict.Set("productIdentifier", payment.productIdentifier); + dict.Set("quantity", payment.quantity); + return dict.GetHandle(); + } +}; -v8::Local Converter::ToV8( - v8::Isolate* isolate, - const in_app_purchase::Transaction& transaction) { - mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); - dict.SetHidden("simple", true); - dict.Set("transactionIdentifier", transaction.transactionIdentifier); - dict.Set("transactionDate", transaction.transactionDate); - dict.Set("originalTransactionIdentifier", - transaction.originalTransactionIdentifier); - dict.Set("transactionState", transaction.transactionState); +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const in_app_purchase::Transaction& val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); + dict.Set("transactionIdentifier", val.transactionIdentifier); + dict.Set("transactionDate", val.transactionDate); + dict.Set("originalTransactionIdentifier", + val.originalTransactionIdentifier); + dict.Set("transactionState", val.transactionState); + dict.Set("errorCode", val.errorCode); + dict.Set("errorMessage", val.errorMessage); + return dict.GetHandle(); + } +}; - dict.Set("errorCode", transaction.errorCode); - dict.Set("errorMessage", transaction.errorMessage); - - return dict.GetHandle(); -} } // namespace mate +namespace atom { + +namespace api { + +// static +mate::Handle InAppPurchase::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new InAppPurchase(isolate)); +} + +// static +void InAppPurchase::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "InAppPurchase")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .SetMethod("canMakePayments", &in_app_purchase::CanMakePayments) + .SetMethod("getReceiptURL", &in_app_purchase::GetReceiptURL) + .SetMethod("purchaseProduct", &InAppPurchase::PurchaseProduct) + .SetMethod("addTransactionListener", + &in_app_purchase::AddTransactionObserver); +} + +InAppPurchase::InAppPurchase(v8::Isolate* isolate) { + Init(isolate); +} + +InAppPurchase::~InAppPurchase() { +} + +void InAppPurchase::PurchaseProduct(const std::string& product_id, + mate::Arguments* args) { + int quantity = 1; + in_app_purchase::InAppPurchaseCallback callback; + args->GetNext(&quantity); + args->GetNext(&callback); + in_app_purchase::PurchaseProduct(product_id, quantity, callback); +} + +} // namespace api + +} // namespace atom + namespace { +using atom::api::InAppPurchase; + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { - mate::Dictionary dict(context->GetIsolate(), exports); #if defined(OS_MACOSX) - dict.SetMethod("canMakePayments", &in_app_purchase::CanMakePayments); - dict.SetMethod("getReceiptURL", &in_app_purchase::GetReceiptURL); - dict.SetMethod("purchaseProduct", &in_app_purchase::PurchaseProduct); - dict.SetMethod("addTransactionListener", - &in_app_purchase::AddTransactionObserver); + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.Set("inAppPurchase", InAppPurchase::Create(isolate)); + dict.Set("InAppPurchase", + InAppPurchase::GetConstructor(isolate)->GetFunction()); #endif } diff --git a/atom/browser/api/atom_api_in_app_purchase.h b/atom/browser/api/atom_api_in_app_purchase.h index 7362f7da6ee9..b11ccbc6607a 100644 --- a/atom/browser/api/atom_api_in_app_purchase.h +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -7,30 +7,34 @@ #include +#include "atom/browser/api/event_emitter.h" #include "atom/browser/mac/in_app_purchase.h" #include "atom/browser/mac/in_app_purchase_observer.h" -#include "native_mate/dictionary.h" +#include "native_mate/handle.h" -namespace mate { +namespace atom { -template <> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const in_app_purchase::Payment& val); - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - in_app_purchase::Payment* out); +namespace api { + +class InAppPurchase: public mate::EventEmitter { + public: + static mate::Handle Create(v8::Isolate* isolate); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + explicit InAppPurchase(v8::Isolate* isolate); + ~InAppPurchase() override; + + void PurchaseProduct(const std::string& product_id, mate::Arguments* args); + + private: + DISALLOW_COPY_AND_ASSIGN(InAppPurchase); }; -template <> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const in_app_purchase::Transaction& val); - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - in_app_purchase::Transaction* out); -}; +} // namespace api -} // namespace mate +} // namespace atom #endif // ATOM_BROWSER_API_ATOM_API_IN_APP_PURCHASE_H_ diff --git a/atom/browser/mac/in_app_purchase.h b/atom/browser/mac/in_app_purchase.h index fc4177affe58..d014744d55c4 100644 --- a/atom/browser/mac/in_app_purchase.h +++ b/atom/browser/mac/in_app_purchase.h @@ -22,7 +22,7 @@ bool CanMakePayments(void); std::string GetReceiptURL(void); void PurchaseProduct(const std::string& productID, - const int quantity, + int quantity, const InAppPurchaseCallback& callback); } // namespace in_app_purchase diff --git a/atom/browser/mac/in_app_purchase.mm b/atom/browser/mac/in_app_purchase.mm index 4204fa509a0d..f9f98332da19 100644 --- a/atom/browser/mac/in_app_purchase.mm +++ b/atom/browser/mac/in_app_purchase.mm @@ -146,9 +146,9 @@ std::string GetReceiptURL() { } void PurchaseProduct(const std::string& productID, - const int quantity, + int quantity, const InAppPurchaseCallback& callback) { - auto iap = + auto* iap = [[InAppPurchase alloc] initWithCallback:callback quantity:quantity]; [iap purchaseProduct:base::SysUTF8ToNSString(productID)]; diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm index a56f6b72298e..1cd0ab9e075e 100644 --- a/atom/browser/mac/in_app_purchase_observer.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -177,6 +177,8 @@ namespace in_app_purchase { void AddTransactionObserver(const InAppTransactionCallback& callback) { + // This is leaked, but we should be fine since we don't have a way to remove + // callback and the inAppPurchase module is never unloaded. [[InAppTransactionObserver alloc] initWithCallback:callback]; } diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md index effbf22af0fe..9019e41c08e2 100644 --- a/docs/api/in-app-purchase.md +++ b/docs/api/in-app-purchase.md @@ -1,14 +1,16 @@ # inAppPurchase _macOS_ +> In-App Purchase on Mac App Store. + Your application should add a listener before to purchase a product. If there are no listener attached to the queue, the payment queue does not synchronize its list of pending transactions with the Apple App Store. -## Methods +## Methods The `inAppPurchase` module has the following methods: ### `inAppPurchase.addTransactionListener(listener)` -* `listener` Function - Called when transactions are updated by the payment queue. +* `listener` Function - Called when transactions are updated by the payment queue. * `payment` Object * `productIdentifier` String * `quantity` Integer @@ -19,9 +21,11 @@ The `inAppPurchase` module has the following methods: * `transactionState` String - The transaction sate (`"SKPaymentTransactionStatePurchasing"`, `"SKPaymentTransactionStatePurchased"`, `"SKPaymentTransactionStateFailed"`, `"SKPaymentTransactionStateRestored"`, or `"SKPaymentTransactionStateDeferred"`) * `errorCode` Integer * `errorMessage` String - + +Add a listener to transactions. ### `inAppPurchase.purchaseProduct(productID, quantity, callback)` + * `productID` String - The id of the product to purchase. (the id of `com.example.app.product1` is `product1`). * `quantity` Integer (optional) - The number of items the user wants to purchase. * `callback` Function (optional) - The callback called when the payment is added to the PaymentQueue. (You should add a listener with `inAppPurchase.addTransactionsListener` to get the transaction status). @@ -33,4 +37,4 @@ Returns `true` if the user can make a payment and `false` otherwise. ### `inAppPurchase.getReceiptURL()` -Returns `String`, the path to the receipt. \ No newline at end of file +Returns `String`, the path to the receipt. diff --git a/lib/browser/api/in-app-purchase.js b/lib/browser/api/in-app-purchase.js index 44487ae02e3f..dc0bffa9c977 100644 --- a/lib/browser/api/in-app-purchase.js +++ b/lib/browser/api/in-app-purchase.js @@ -1,39 +1,10 @@ 'use strict' -const binding = process.atomBinding('in_app_purchase') -const v8Util = process.atomBinding('v8_util') +const {EventEmitter} = require('events') +const {inAppPurchase, InAppPurchase} = process.atomBinding('in_app_purchase') -module.exports = { +// inAppPurchase is an EventEmitter. +Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype) +EventEmitter.call(inAppPurchase) - canMakePayments: function () { - return binding.canMakePayments() - }, - - getReceiptURL: function () { - return binding.getReceiptURL() - }, - - purchaseProduct: function (productID, quantity = 1, callback) { - if (typeof productID !== 'string') { - throw new TypeError('productID must be a string') - } - if (typeof quantity === 'function') { - quantity = 1 - callback = quantity - } - return binding.purchaseProduct(productID, quantity, callback) - }, - - addTransactionListener: function (callback) { - if (typeof callback !== 'function') { - throw new TypeError('callback must be a function') - } - return binding.addTransactionListener(callback) - } -} - -// Mark standard asynchronous functions. -v8Util.setHiddenValue( - module.exports.purchaseProduct, 'asynchronous', true) -v8Util.setHiddenValue( - module.exports.addTransactionListener, 'asynchronous', true) +module.exports = inAppPurchase diff --git a/spec/api-in-app-purchase-spec.js b/spec/api-in-app-purchase-spec.js index 541a2af2f633..961ae22fe19b 100644 --- a/spec/api-in-app-purchase-spec.js +++ b/spec/api-in-app-purchase-spec.js @@ -22,4 +22,11 @@ describe('inAppPurchase module', () => { done() }) }) + + it('purchaseProduct() accepts optional arguments', (done) => { + inAppPurchase.purchaseProduct('non-exist', () => { + inAppPurchase.purchaseProduct('non-exist', 1) + done() + }) + }) })