How to import a Cordova plugin with TypeScript, React, and Vite?

957 Views Asked by At

I have a TypeScript React project that is built in Vite. This is an Ionic React project that I build apps for Android and iOS as well as a PWA for the web.

I'm trying to use the latest version (13) of the cordova-purchase-plugin in my app. This version adds TypeScript support but it is not a module, so I'm confused about how to input it correctly (everything else in my app that I import is a module).

A very simple code example:

import 'cordova-plugin-purchase';

const store = new CdvPurchase.Store();

When I build this in Vite, it compiles with no errors. In VSCode, I can manipulate the store object and the plugin's built-in types are shown correctly.

However, when I open the PWA in my web browser, I get an error:

Can't find variable: CdvPurchase

So the import is failing somehow.

cordova-plugin-purchase includes a single JS file, store.js.

To get my compiled app to load, I can copy this store.js file into the assets directory of my app and then add it via the <script> tag in index.html. This puts CdvPurchase in global scope and allows my app to load. However, I obviously don't want to be manually adding scripts from node_modules to index.html-- that's what a build tool is for.

So how can I make sure the variable is imported/resolve this error?

More background

Previously, I was using the awesome-cordova-plugins wrapper to install the cordova-purchase-plugin. This works, but awesome-cordova-plugins is limited to cordova-purchase-plugin version 11, and I am trying to find a way to use version 13 in my app.

1

There are 1 best solutions below

0
Patrick Kenny On BEST ANSWER

Here's how to use cordova-purchase-plugin with React 18 and Ionic React 7, TypeScript 5.

Partial example:

import React from 'react';
import 'cordova-plugin-purchase';
// Some imports omitted.

const PageStoreAppFovea: React.FC = () => {
  const history = useHistory();

  // These are wrappers for useState() hooks.    
  const [isLoading, setIsLoading] = useLoading();
  const [showErrorAlert, setShowErrorAlert] = useAlert();
  const [showCancelledAlert, setShowCancelledAlert] = useAlert();
  const [showSuccessAlert, setShowSuccessAlert] = useAlert();

  const [isVerifying, setIsVerifying] = useStateBoolean();

  const userObject = useUserObject();
  const queryClient = useQueryClient();

  const { store } = CdvPurchase;

  const monthly = store.get(MyProduct.SubMonthly);
  const annual = store.get(MyProduct.SubAnnual);
    
  const buySub = (sub: CdvPurchase.Offer) => {
    const productId = sub.id;
    setIsLoading(true);
    // https://bobbyhadz.com/blog/typescript-check-if-value-exists-in-enum
    const allProductsValues = Object.values(MyProduct);
    if (allProductsValues.includes(productId)) {
      // console.log('placing order for ', productId);
      store.applicationUsername = () => userObject.id;
      store
        .order(sub)
        .then(() => {
          console.log('order placed', store.get(productId));
        })
        .catch((error: Error) => {
          console.log('error purchased failed', error);
          setShowErrorAlert(true);
        });
    } else {
      const errorMessage = `Product is invalid: ${productId}`;
      throw new Error(errorMessage);
    }
  };
  // User closed the native purchase dialog
  store.when().productUpdated((product) => {
    console.log('Purchase cancelled', product);
    setIsLoading(false);
    setShowCancelledAlert(true);
  });

  // Upon approval, show a different message.
  store.when().approved((product) => {
    console.log('Purchase approved', product);
    setIsVerifying(true);
  });

  // Upon the subscription becoming owned.
  store.when().finished((product) => {
    console.log('Purchase now owned', product);
    queryClient
      .invalidateQueries({ queryKey: ['myKey'] })
      .then(() => setShowSuccessAlert(true))
  });

  const onClickCancelNotDuringVerify = () => {
    setIsLoading(false);
  };

  const onClickCancelDuringVerify = () => {
    setIsVerifying(false);
    setIsLoading(false);
  };

  // console.log('monthly', monthly);
  // console.log('annual', annual);
  // Todo: Show a message if the free trial is in progress.
  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonBackButton defaultHref={'my-route'} />
          </IonButtons>
          <IonTitle>
            <TTitleStore />
          </IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        {!userObject.hasAccessPaidFeatures(myCookie) && (
          <BlockSubscriptionExpired />
        )}
        <p>Mobile store</p>
        {isLoading && !isVerifying && (
          <>
            <p>Please wait...</p>
            <ButtonStoreCancel onClick={onClickCancelNotDuringVerify} />
          </>
        )}
        {isLoading && isVerifying && (
          <>
            <p>Please wait...</p>
            <ButtonStoreCancel onClick={onClickCancelDuringVerify} />
          </>
        )}
        {!isLoading && !isVerifying && monthly && annual && (
          <ListSubscriptions
            monthly={monthly}
            annual={annual}
            buySub={buySub}
            setIsLoading={setIsLoading}
          />
        )}
        <IonAlert
          isOpen={showErrorAlert}
          onDidDismiss={() => setShowErrorAlert(false)}
          message={tAlertMessagePurchaseFailed}
          buttons={['OK']}
        />
        <IonAlert
          isOpen={showCancelledAlert}
          onDidDismiss={() => setShowCancelledAlert(false)}
          message={tAlertMessagePurchaseCancelled}
          buttons={['OK']}
        />
        <IonAlert
          isOpen={showSuccessAlert}
          onDidDismiss={() => {
            history.push(routeTabWelcome);
          }}
          message={tAlertMessagePurchaseSuccess}
          buttons={['OK']}
        />
      </IonContent>
    </IonPage>
  );
};

export default PageStoreAppFovea;