j3k0 / cordova-plugin-purchase

In-App Purchase for Cordova on iOS, Android and Windows
https://purchase.cordova.fovea.cc
1.3k stars 537 forks source link

[ANDROID] - Retries of `init()` keep pushing already added skus #1287

Closed bitbay closed 1 year ago

bitbay commented 2 years ago

Expected behavior

Initialization should check already registered skus before pushing them again into the inner sku[] property. plugin-adapter.js#L49

Observed behavior

If initialization fails for any reason, subsequent retries keep adding to the store.sku array already registered items, making it growing infinitely (probably causing memory issues also down the line). Probably happens with subSkus[] and inAppSkus[] as well.

Steps to reproduce

I think the easiest way is to log out from Google Play, but in my case i'm trying to handle gracefully the case when the device has an incompatible version of it (AFIK this is the reason for the plugin not initializing correctly and returning an error of {code: 6777001, message: "Init failed - Setup failed. BILLING_UNAVAILABLE: Google Play In-app Billing API version is less than 3"}...)

After that, run in client code

// set verbose logging
store.verbosity = this.store.DEBUG;

// register a product
store.register({
  id: "product_sku",
  type: store.CONSUMABLE
});

// won't get called, since store initing fails, and keeps auto-retrying over and over again...
store.ready(function() {
  console.log("\\o/ STORE READY \\o/");
});

// call refresh() to init the plugin...
store.refresh();
// log output
InAppBilling[js]: load ["product_sku"]
...
InAppBilling[js]: load ["product_sku", "product_sku"]
...
InAppBilling[js]: load ["product_sku", "product_sku", "product_sku"]
...
bitbay commented 2 years ago

A plain, no-brainer solution could be filtering the three arrays for duplicates

function init() {
    if (initialized) return;
    initialized = true;

    for (var i = 0; i < store.products.length; ++i) {
      skus.push(store.products[i].id);
      if (store.products[i].type === store.PAID_SUBSCRIPTION)
        subsSkus.push(store.products[i].id);
      else
        inAppSkus.push(store.products[i].id);
    }

    skus = [ ...new Set(skus) ];
    subsSkus = [ ...new Set(subsSkus) ];
    inAppSkus = [ ...new Set(inAppSkus ) ];
    ...

Or by doing a previous check like

    for (var i = 0; i < store.products.length; ++i) {
      var product = store.products[i];
      if(!skus.includes(product.id)) skus.push(product.id);
      if(product.type === store.PAID_SUBSCRIPTION && !subsSkus.includes(product.id)) {
          subsSkus.push(product.id);
      } else if(!inAppSkus.includes(product.id)) {
          inAppSkus.push(product.id);
      }
    }

Although not sure about how and if the "inner state" of the product should affect this...

bitbay commented 2 years ago

Also, maybe this retry logic should be moved to the client of the plugin, or at least expose the number of init retries through config or a public property (0 meaning no auto-retry initing), but this isn't part of this issue.

j3k0 commented 1 year ago

Fixed in version 13 of the plugin with architecture change (no persistent list of skus anymore).