vitecapacitorionic-react

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


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.


Solution

  • 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;