# Source: index.js

    "use strict";

    import {Platform, NativeModules} from "react-native";
    import packageJson from "./package.json";
    const {MixpanelReactNative} = NativeModules;
    import MixpanelMain from "mixpanel-react-native/javascript/mixpanel-main"

    const DevicePlatform = {
      Unknown: "Unknown",
      Android: "android",
      iOS: "ios",
    };

    const ERROR_MESSAGE = {
      INVALID_OBJECT: " is not a valid json object",
      INVALID_STRING: " is not a valid string",
      REQUIRED_DOUBLE: " is not a valid number",
    };

    const PARAMS = {
      TOKEN: "token",
      DISTINCT_ID: "distinctId",
      ALIAS: "alias",
      EVENT_NAME: "eventName",
      GROUP_KEY: "groupKey",
      PROPERTIES: "properties",
      PROPERTY_NAME: "propertyName",
      PROP: "prop",
      NAME: "name",
      CHARGE: "charge",
      PROPERTY_VALUE: "property value",
    };

    const DEFAULT_OPT_OUT = false;

    /**
     * The primary class for integrating Mixpanel with your app.
     */
    export class Mixpanel {
      constructor(token, trackAutomaticEvents, useNative = true, storage) {
        if (!StringHelper.isValid(token)) {
          StringHelper.raiseError(PARAMS.TOKEN);
        }
        if (trackAutomaticEvents == null) {
          throw new Error(`trackAutomaticEvents is undefined`);
        }
        this.token = token;
        this.trackAutomaticEvents = trackAutomaticEvents;

        if (useNative && MixpanelReactNative) {
          this.mixpanelImpl = MixpanelReactNative;
          return;
        } else if (useNative) {
          console.warn(
            "MixpanelReactNative is not available; using JavaScript mode. If you prefer not to use the JavaScript mode, please follow the guide in the GitHub repository: https://github.com/mixpanel/mixpanel-react-native."
          );
        }

        this.mixpanelImpl = new MixpanelMain(token, trackAutomaticEvents, storage);
      }

      /**
       * Initializes Mixpanel
       *
       * @param {boolean} optOutTrackingDefault Optional Whether or not Mixpanel can start tracking by default. See optOutTracking()
       * @param {object} superProperties  Optional A Map containing the key value pairs of the super properties to register
       * @param {string} serverURL Optional Set the base URL used for Mixpanel API requests. See setServerURL()
       *
       */
      async init(
        optOutTrackingDefault = DEFAULT_OPT_OUT,
        superProperties = {},
        serverURL = "https://api.mixpanel.com"
      ) {
        await this.mixpanelImpl.initialize(
          this.token,
          this.trackAutomaticEvents,
          optOutTrackingDefault,
          {...Helper.getMetaData(), ...superProperties},
          serverURL
        );
      }

      /**
       * @deprecated since version 1.3.0. To initialize Mixpanel, please use the instance method `init` instead. See the example below:
       *
       * <pre><code>
       * const trackAutomaticEvents = true;
       * const mixpanel = new Mixpanel('your project token', trackAutomaticEvents);
       * mixpanel.init();
       * </code></pre>
       *
       * Initializes Mixpanel and return an instance of Mixpanel the given project token.
       *
       * @param {string} token your project token.
       * @param {boolean} trackAutomaticEvents Whether or not to automatically track common mobile events
       * @param {boolean} Optional Whether or not Mixpanel can start tracking by default. See optOutTracking()
       *
       */
      static async init(
        token,
        trackAutomaticEvents,
        optOutTrackingDefault = DEFAULT_OPT_OUT
      ) {
        await MixpanelReactNative.initialize(
          token,
          trackAutomaticEvents,
          optOutTrackingDefault,
          Helper.getMetaData(),
          "https://api.mixpanel.com"
        );
        return new Mixpanel(token, trackAutomaticEvents);
      }

      /**
       * Set the base URL used for Mixpanel API requests.
       * Useful if you need to proxy Mixpanel requests. Defaults to https://api.mixpanel.com.
       * To route data to Mixpanel's EU servers, set to https://api-eu.mixpanel.com
       *
       * @param {string} serverURL the base URL used for Mixpanel API requests
       *
       */
      setServerURL(serverURL) {
        this.mixpanelImpl.setServerURL(this.token, serverURL);
      }

      /**
       * This allows enabling or disabling of all Mixpanel logs at run time.
       * All logging is disabled by default. Usually, this is only required if
       * you are running into issues with the SDK that you want to debug
       *
       * @param {boolean} loggingEnabled whether to enable logging
       *
       */
      setLoggingEnabled(loggingEnabled) {
        this.mixpanelImpl.setLoggingEnabled(this.token, loggingEnabled);
      }

      /**
       * This allows enabling or disabling whether or not Mixpanel flushes events
       * when the app enters the background on iOS. This is set to true by default.
       *
       * @param {boolean} flushOnBackground whether to enable logging
       *
       */
      setFlushOnBackground(flushOnBackground) {
        if (Platform.OS === "ios") {
          MixpanelReactNative.setFlushOnBackground(this.token, flushOnBackground);
        } else {
          console.warn(
            "Mixpanel setFlushOnBackground was called and ignored because this method only works on iOS."
          );
        }
      }

      /**
       * This controls whether to automatically send the client IP Address as part of event tracking.
       * With an IP address, geo-location is possible down to neighborhoods within a city,
       * although the Mixpanel Dashboard will just show you city level location specificity.
       *
       * @param {boolean} useIpAddressForGeolocation whether to automatically send the client IP Address.
       * Defaults to true.
       *
       */
      setUseIpAddressForGeolocation(useIpAddressForGeolocation) {
        this.mixpanelImpl.setUseIpAddressForGeolocation(
          this.token,
          useIpAddressForGeolocation
        );
      }

      /**
       * Set the number of events sent in a single network request to the Mixpanel server.
       * By configuring this value, you can optimize network usage and manage the frequency of communication between the client and the server. The maximum size is 50; any value over 50 will default to 50.
       *
       * @param {integer} flushBatchSize whether to automatically send the client IP Address.
       * Defaults to true.
       *
       */
      setFlushBatchSize(flushBatchSize) {
        this.mixpanelImpl.setFlushBatchSize(this.token, flushBatchSize);
      }

      /**
       * Will return true if the user has opted out from tracking.
       *
       * @return {Promise<boolean>} true if user has opted out from tracking. Defaults to false.
       */
      hasOptedOutTracking() {
        return this.mixpanelImpl.hasOptedOutTracking(this.token);
      }

      /**
       * Use this method to opt-in an already opted-out user from tracking. People updates and track
       * calls will be sent to Mixpanel after using this method.
       * This method will internally track an opt-in event to your project.
       *
       */
      optInTracking() {
        this.mixpanelImpl.optInTracking(this.token);
      }

      /**
       * Use this method to opt-out a user from tracking. Events and people updates that haven't been
       * flushed yet will be deleted. Use flush() before calling this method if you want
       * to send all the queues to Mixpanel before.
       *
       * This method will also remove any user-related information from the device.
       */
      optOutTracking() {
        this.mixpanelImpl.optOutTracking(this.token);
      }

      /**
       * Associate all future calls to track() with the user identified by
       * the given distinct id.
       *
       * <p>Calls to track() made before corresponding calls to identify
       * will use an anonymous locally generated distinct id, which means it is best to call identify
       * early to ensure that your Mixpanel funnels and retention analytics can continue to track the
       * user throughout their lifetime. We recommend calling identify when the user authenticates.
       *
       * <p>Once identify is called, the local distinct id persists across restarts of
       * your application.
       *
       * @param {string} distinctId a string uniquely identifying this user. Events sent to
       *     Mixpanel using the same disinct_id will be considered associated with the
       *     same visitor/customer for retention and funnel reporting, so be sure that the given
       *     value is globally unique for each individual user you intend to track.
       * @returns {Promise} A promise that resolves when the identify is successful.
       *     It does not return any value.
       *
       */
      identify(distinctId) {
        return new Promise((resolve, reject) => {
          if (!StringHelper.isValid(distinctId)) {
            StringHelper.raiseError(PARAMS.DISTINCT_ID);
            reject(new Error("Invalid distinctId"));
          }
          this.mixpanelImpl
            .identify(this.token, distinctId)
            .then(() => {
              resolve();
            })
            .catch((err) => {
              reject(err);
            });
        });
      }

      /**
       * @deprecated The alias method creates an alias which Mixpanel will use to remap one id to another.
       * Multiple aliases can point to the same identifier.
       *
       *  `mixpane.alias("New ID", mixpane.distinctId)`
       *  `mixpane.alias("Newer ID", mixpane.distinctId)`
       *
       * <p>This call does not identify the user after. You must still call identify()
       *  if you wish the new alias to be used for Events and People.
       *
       * @param {string} alias A unique identifier that you want to use as an identifier for this user.
       * @param {string} distinctId the current distinct_id that alias will be mapped to.
       */
      alias(alias, distinctId) {
        if (!StringHelper.isValid(alias)) {
          StringHelper.raiseError(PARAMS.ALIAS);
        }
        if (!StringHelper.isValid(distinctId)) {
          StringHelper.raiseError(PARAMS.DISTINCT_ID);
        }
        this.mixpanelImpl.alias(this.token, alias, distinctId);
      }

      /**
       * Track an event.
       *
       * <p>Every call to track eventually results in a data point sent to Mixpanel. These data points
       * are what are measured, counted, and broken down to create your Mixpanel reports. Events
       * have a string name, and an optional set of name/value pairs that describe the properties of
       * that event.
       *
       * @param {string} eventName The name of the event to send
       * @param {object} properties A Map containing the key value pairs of the properties to include in this event.
       *                   Pass null if no extra properties exist.
       */
      track(eventName, properties) {
        if (!StringHelper.isValid(eventName)) {
          StringHelper.raiseError(PARAMS.EVENT_NAME);
        }
        if (!ObjectHelper.isValidOrUndefined(properties)) {
          ObjectHelper.raiseError(PARAMS.PROPERTIES);
        }
        this.mixpanelImpl.track(this.token, eventName, {
          ...Helper.getMetaData(),
          ...properties,
        });
      }

      /**
       * Returns a Mixpanel People object that can be used to set and increment
       * People Analytics properties.
       *
       * @return {People} an instance of People that you can use to update
       *     records in Mixpanel People Analytics
       */
      getPeople() {
        if (this.people) {
          return this.people;
        } else {
          this.people = new People(this.token, this.mixpanelImpl);
          return this.people;
        }
      }

      /**
       * Track an event with specific groups.
       *
       * <p>Every call to track eventually results in a data point sent to Mixpanel. These data points
       * are what are measured, counted, and broken down to create your Mixpanel reports. Events
       * have a string name, and an optional set of name/value pairs that describe the properties of
       * that event. Group key/value pairs are upserted into the property map before tracking.
       *
       * @param {string} eventName The name of the event to send
       * @param {object} properties A Map containing the key value pairs of the properties to include in this event.
       *                   Pass null if no extra properties exist.
       * @param {object} groups A Map containing the group key value pairs for this event.
       *
       */
      trackWithGroups(eventName, properties, groups) {
        if (!StringHelper.isValid(eventName)) {
          StringHelper.raiseError(PARAMS.EVENT_NAME);
        }
        if (!ObjectHelper.isValidOrUndefined(properties)) {
          ObjectHelper.raiseError(PARAMS.PROPERTIES);
        }
        this.mixpanelImpl.trackWithGroups(
          this.token,
          eventName,
          {
            ...Helper.getMetaData(),
            ...properties,
          },
          groups
        );
      }

      /**
       * Set the group this user belongs to.
       *
       * @param {string} groupKey The property name associated with this group type (must already have been set up).
       * @param {object} groupID The group the user belongs to.
       */
      setGroup(groupKey, groupID) {
        if (!StringHelper.isValid(groupKey)) {
          StringHelper.raiseError(PARAMS.GROUP_KEY);
        }
        this.mixpanelImpl.setGroup(this.token, groupKey, groupID);
      }

      /**
       * Returns a MixpanelGroup object that can be used to set and increment
       * Group Analytics properties.
       *
       * @param {string} groupKey String identifying the type of group (must be already in use as a group key)
       * @param {object} groupID Object identifying the specific group
       * @return an instance of MixpanelGroup that you can use to update
       *     records in Mixpanel Group Analytics
       */
      getGroup(groupKey, groupID) {
        if (this.group) {
          return this.group;
        } else {
          this.group = new MixpanelGroup(
            this.token,
            groupKey,
            groupID,
            this.mixpanelImpl
          );
          return this.group;
        }
      }

      /**
       * Add a group to this user's membership for a particular group key
       *
       * @param {string} groupKey The property name associated with this group type (must already have been set up).
       * @param {object} groupID The new group the user belongs to.
       */
      addGroup(groupKey, groupID) {
        if (!StringHelper.isValid(groupKey)) {
          StringHelper.raiseError(PARAMS.GROUP_KEY);
        }
        this.mixpanelImpl.addGroup(this.token, groupKey, groupID);
      }

      /**
       * Remove a group from this user's membership for a particular group key
       *
       * @param {string} groupKey The property name associated with this group type (must already have been set up).
       * @param {object} groupID The group value to remove.
       */
      removeGroup(groupKey, groupID) {
        if (!StringHelper.isValid(groupKey)) {
          StringHelper.raiseError(PARAMS.GROUP_KEY);
        }
        this.mixpanelImpl.removeGroup(this.token, groupKey, groupID);
      }

      /**
       * Permanently deletes this group's record from Group Analytics.
       *
       * @param {string} groupKey String identifying the type of group (must be already in use as a group key)
       * @param {object} groupID Object identifying the specific group
       * <p>Calling deleteGroup deletes an entire record completely. Any future calls
       * to Group Analytics using the same group value will create and store new values.
       */
      deleteGroup(groupKey, groupID) {
        if (!StringHelper.isValid(groupKey)) {
          StringHelper.raiseError(PARAMS.GROUP_KEY);
        }
        this.mixpanelImpl.deleteGroup(this.token, groupKey, groupID);
      }

      /**
       * Register properties that will be sent with every subsequent call to track().
       *
       * <p>SuperProperties are a collection of properties that will be sent with every event to Mixpanel,
       * and persist beyond the lifetime of your application.
       *
       * <p>Setting a superProperty with registerSuperProperties will store a new superProperty,
       * possibly overwriting any existing superProperty with the same name (to set a
       * superProperty only if it is currently unset, use registerSuperPropertiesOnce())
       *
       * <p>SuperProperties will persist even if your application is taken completely out of memory.
       * to remove a superProperty, call unregisterSuperProperty() or clearSuperProperties()
       *
       * @param {object} properties A Map containing super properties to register
       */
      registerSuperProperties(properties) {
        if (!ObjectHelper.isValidOrUndefined(properties)) {
          ObjectHelper.raiseError(PARAMS.PROPERTIES);
        }
        this.mixpanelImpl.registerSuperProperties(this.token, properties || {});
      }

      /**
       * Register super properties for events, only if no other super property with the
       * same names has already been registered.
       *
       * <p>Calling registerSuperPropertiesOnce will never overwrite existing properties.
       *
       * @param {object} properties A Map containing the super properties to register.
       */
      registerSuperPropertiesOnce(properties) {
        if (!ObjectHelper.isValidOrUndefined(properties)) {
          ObjectHelper.raiseError(PARAMS.PROPERTIES);
        }
        this.mixpanelImpl.registerSuperPropertiesOnce(this.token, properties || {});
      }

      /**
       * Remove a single superProperty, so that it will not be sent with future calls to track().
       *
       * <p>If there is a superProperty registered with the given name, it will be permanently
       * removed from the existing superProperties.
       * To clear all superProperties, use clearSuperProperties()
       *
       * @param {string} propertyName name of the property to unregister
       */
      unregisterSuperProperty(propertyName) {
        if (!StringHelper.isValid(propertyName)) {
          StringHelper.raiseError(PARAMS.PROPERTY_NAME);
        }
        this.mixpanelImpl.unregisterSuperProperty(this.token, propertyName);
      }

      /**
       * Returns a json object of the user's current super properties
       *
       *<p>SuperProperties are a collection of properties that will be sent with every event to Mixpanel,
       * and persist beyond the lifetime of your application.
       *
       * @return {Promise<object>} Super properties for this Mixpanel instance.
       */
      getSuperProperties() {
        return this.mixpanelImpl.getSuperProperties(this.token);
      }

      /**
       * Erase all currently registered superProperties.
       *
       * <p>Future tracking calls to Mixpanel will not contain the specific
       * superProperties registered before the clearSuperProperties method was called.
       *
       * <p>To remove a single superProperty, use unregisterSuperProperty()
       */
      clearSuperProperties() {
        this.mixpanelImpl.clearSuperProperties(this.token);
      }

      /**
       * Begin timing of an event. Calling timeEvent("Thing") will not send an event, but
       * when you eventually call track("Thing"), your tracked event will be sent with a "$duration"
       * property, representing the number of seconds between your calls.
       *
       * @param {string} eventName the name of the event to track with timing.
       */
      timeEvent(eventName) {
        if (!StringHelper.isValid(eventName)) {
          StringHelper.raiseError(PARAMS.EVENT_NAME);
        }
        this.mixpanelImpl.timeEvent(this.token, eventName);
      }

      /**
       * Retrieves the time elapsed for the named event since timeEvent() was called.
       *
       * @param {string} eventName the name of the event to be tracked that was previously called with timeEvent()
       *
       * @return {Promise<number>} Time elapsed since timeEvent(String) was called for the given eventName.
       */
      eventElapsedTime(eventName) {
        if (!StringHelper.isValid(eventName)) {
          StringHelper.raiseError(PARAMS.EVENT_NAME);
        }
        return this.mixpanelImpl.eventElapsedTime(this.token, eventName);
      }

      /**
          Clear super properties and generates a new random distinctId for this instance.
          Useful for clearing data when a user logs out.
         */
      reset() {
        this.mixpanelImpl.reset(this.token);
      }

      /**
       * Returns the current distinct id of the user.
       * This is either the id automatically generated by the library or the id that has been passed by a call to identify().
       *
       * example of usage:
       * <pre>
       * <code>
       * const distinctId = await mixpanel.getDistinctId();
       * </code>
       * </pre>
       *
       * @return {Promise<string>} A Promise to the distinct id associated with Mixpanel event and People Analytics
       *
       */
      getDistinctId() {
        return this.mixpanelImpl.getDistinctId(this.token);
      }

      /**
       * Returns the current device id of the device.
       * This id automatically generated by the library and regenerated when logout or reset is called.
       *
       * example of usage:
       * <pre>
       * <code>
       * const deviceId = await mixpanel.getDeviceId();
       * </code>
       * </pre>
       *
       * @return {Promise<string>} A Promise to the device id
       *
       */
      getDeviceId() {
        return this.mixpanelImpl.getDeviceId(this.token);
      }

      /**
       * Push all queued Mixpanel events and People Analytics changes to Mixpanel servers.
       *
       * <p>Events and People messages are pushed gradually throughout
       * the lifetime of your application. This means that to ensure that all messages
       * are sent to Mixpanel when your application is shut down, you will
       * need to call flush() to let the Mixpanel library know it should
       * send all remaining messages to the server.
       */
      flush() {
        this.mixpanelImpl.flush(this.token);
      }
    }

    /**
     * Core class for using Mixpanel People Analytics features.
     *
     * <p>The People object is used to update properties in a user's People Analytics record,
     * and to manage the receipt of push notifications sent via Mixpanel Engage.
     * For this reason, it's important to call identify(String) on the People
     * object before you work with it. Once you call identify, the user identity will
     * persist across stops and starts of your application, until you make another
     * call to identify using a different id.
     *
     */
    export class People {
      constructor(token, mixpanelImpl) {
        if (!StringHelper.isValid(token)) {
          StringHelper.raiseError(PARAMS.TOKEN);
        }
        this.token = token;
        this.mixpanelImpl = mixpanelImpl;
      }

      /**
       * Sets a single property with the given name and value for this user.
       * The given name and value will be assigned to the user in Mixpanel People Analytics,
       * possibly overwriting an existing property with the same name.
       *
       * @param {string} prop The name of the Mixpanel property. This must be a String, for example "Zip Code"
       * @param {object} to The value of the Mixpanel property. For "Zip Code", this value might be the String "90210"
       */
      set(prop, to) {
        let properties = {};
        if (ObjectHelper.isValid(prop)) {
          properties = JSON.parse(JSON.stringify(prop || {}));
        } else {
          if (!StringHelper.isValid(prop)) {
            StringHelper.raiseError(PARAMS.PROP);
          }
          properties[prop] = to;
        }
        this.mixpanelImpl.set(this.token, properties);
      }

      /**
       * Works just like set(), except it will not overwrite existing property values. This is useful for properties like "First login date".
       *
       * @param {string} prop The name of the Mixpanel property. This must be a String, for example "Zip Code"
       * @param {object} to The value of the Mixpanel property. For "Zip Code", this value might be the String "90210"
       */
      setOnce(prop, to) {
        let properties = {};
        if (ObjectHelper.isValid(prop)) {
          prop = prop || {};
          properties = JSON.parse(JSON.stringify(prop));
        } else {
          if (!StringHelper.isValid(prop)) {
            StringHelper.raiseError(PARAMS.PROP);
          }
          properties[prop] = to;
        }
        this.mixpanelImpl.setOnce(this.token, properties);
      }

      /**
       * Add the given amount to an existing property on the identified user. If the user does not already
       * have the associated property, the amount will be added to zero. To reduce a property,
       * provide a negative number for the value.
       *
       * @param {string} prop the People Analytics property that should have its value changed
       * @param {number} by the amount to be added to the current value of the named property
       *
       */
      increment(prop, by) {
        var add = {};
        if (ObjectHelper.isValid(prop)) {
          Object.keys(prop).forEach(function (key) {
            var val = prop[key];
            if (isNaN(parseFloat(val))) {
              throw new Error(
                `${PARAMS.PROPERTY_VALUE}${ERROR_MESSAGE.REQUIRED_DOUBLE}`
              );
            }
            add[key] = val;
          });
        } else {
          by = by || 1;
          if (isNaN(parseFloat(by))) {
            throw new Error(
              `${PARAMS.PROPERTY_VALUE}${ERROR_MESSAGE.REQUIRED_DOUBLE}`
            );
          }

          if (!StringHelper.isValid(prop)) {
            StringHelper.raiseError(PARAMS.NAME);
          }

          add[prop] = by;
        }
        this.mixpanelImpl.increment(this.token, add);
      }

      /**
       * Appends a value to a list-valued property. If the property does not currently exist,
       * it will be created as a list of one element. If the property does exist and doesn't
       * currently have a list value, the append will be ignored.
       * @param {string} name the People Analytics property that should have it's value appended to
       * @param {object} value the new value that will appear at the end of the property's list
       */
      append(name, value) {
        let appendProp = {};
        if (!StringHelper.isValid(name)) {
          StringHelper.raiseError(PARAMS.NAME);
        } else {
          appendProp[name] = value;
        }

        if (DevicePlatform.iOS === Helper.getDevicePlatform()) {
          this.mixpanelImpl.append(this.token, appendProp);
        } else {
          this.mixpanelImpl.append(this.token, name, value);
        }
      }

      /**
       * Adds values to a list-valued property only if they are not already present in the list.
       * If the property does not currently exist, it will be created with the given list as it's value.
       * If the property exists and is not list-valued, the union will be ignored.
       *
       * @param {string} name name of the list-valued property to set or modify
       * @param {array} value an array of values to add to the property value if not already present
       */
      union(name, value) {
        if (!StringHelper.isValid(name)) {
          StringHelper.raiseError(PARAMS.NAME);
        }

        value = Array.isArray(value) ? value : [value];

        if (DevicePlatform.iOS === Helper.getDevicePlatform()) {
          this.mixpanelImpl.union(this.token, {[name]: value});
          this.mixpanelImpl.union(this.token, {[name]: value});
        } else {
          this.mixpanelImpl.union(this.token, name, value);
        }
      }

      /**
       * Remove value from a list-valued property only if they are already present in the list.
       * If the property does not currently exist, the remove will be ignored.
       * If the property exists and is not list-valued, the remove will be ignored.
       * @param {string} name the People Analytics property that should have it's value removed from
       * @param {object} value the value that will be removed from the property's list
       */
      remove(name, value) {
        let removeProp = {};
        if (!StringHelper.isValid(name)) {
          StringHelper.raiseError(PARAMS.NAME);
        } else {
          removeProp[name] = value;
        }

        if (DevicePlatform.iOS === Helper.getDevicePlatform()) {
          this.mixpanelImpl.remove(this.token, removeProp);
        } else {
          this.mixpanelImpl.remove(this.token, name, value);
        }
      }

      /**
       * permanently removes the property with the given name from the user's profile
       * @param {string} name name of a property to unset
       */
      unset(name) {
        if (!StringHelper.isValid(name)) {
          StringHelper.raiseError(PARAMS.PROPERTY_NAME);
        }
        this.mixpanelImpl.unset(this.token, name);
      }

      /**
       * Track a revenue transaction for the identified people profile.
       *
       * @param {number} charge the amount of money exchanged. Positive amounts represent purchases or income from the customer, negative amounts represent refunds or payments to the customer.
       * @param {object} properties an optional collection of properties to associate with this transaction.
       */
      trackCharge(charge, properties) {
        if (isNaN(parseFloat(charge))) {
          throw new Error(`${PARAMS.CHARGE}${ERROR_MESSAGE.REQUIRED_DOUBLE}`);
        }

        if (!ObjectHelper.isValidOrUndefined(properties)) {
          ObjectHelper.raiseError(PARAMS.PROPERTIES);
        }
        this.mixpanelImpl.trackCharge(this.token, charge, properties || {});
      }

      /**
       * Permanently clear the whole transaction history for the identified people profile.
       */
      clearCharges() {
        this.mixpanelImpl.clearCharges(this.token);
      }

      /**
       * Permanently deletes the identified user's record from People Analytics.
       *
       * <p>Calling deleteUser deletes an entire record completely. Any future calls
       * to People Analytics using the same distinct id will create and store new values.
       */
      deleteUser() {
        this.mixpanelImpl.deleteUser(this.token);
      }
    }

    /**
     * Core class for using Mixpanel Group Analytics features.
     *
     * <p>The MixpanelGroup object is used to update properties in a group's Group Analytics record.
     */
    export class MixpanelGroup {
      constructor(token, groupKey, groupID, mixpanelImpl) {
        if (!StringHelper.isValid(token)) {
          StringHelper.raiseError(PARAMS.TOKEN);
        }
        this.token = token;
        this.groupKey = groupKey;
        this.groupID = groupID;
        this.mixpanelImpl = mixpanelImpl;
      }

      /**
       * Sets a single property with the given name and value for this group.
       * The given name and value will be assigned to the user in Mixpanel Group Analytics,
       * possibly overwriting an existing property with the same name.
       *
       * @param {string} prop The name of the Mixpanel property. This must be a String, for example "Zip Code"
       * @param {string} to The value to set on the given property name. For "Zip Code", this value might be the String "90210"
       */
      set(prop, to) {
        let properties = {};
        if (ObjectHelper.isValid(prop)) {
          properties = JSON.parse(JSON.stringify(prop || {}));
        } else {
          if (!StringHelper.isValid(prop)) {
            StringHelper.raiseError(PARAMS.PROP);
          }
          properties[prop] = to;
        }
        this.mixpanelImpl.groupSetProperties(
          this.token,
          this.groupKey,
          this.groupID,
          properties
        );
      }

      /**
       * Works just like groupSet() except it will not overwrite existing property values. This is useful for properties like "First login date".
       *
       * @param {string} prop The name of the Mixpanel property. This must be a String, for example "Zip Code"
       * @param {string} to The value to set on the given property name. For "Zip Code", this value might be the String "90210"
       */
      setOnce(prop, to) {
        let properties = {};
        if (ObjectHelper.isValid(prop)) {
          properties = JSON.parse(JSON.stringify(prop || {}));
        } else {
          if (!StringHelper.isValid(prop)) {
            StringHelper.raiseError(PARAMS.PROP);
          }
          properties[prop] = to;
        }
        this.mixpanelImpl.groupSetPropertyOnce(
          this.token,
          this.groupKey,
          this.groupID,
          properties
        );
      }

      /**
       * Permanently removes the property with the given name from the group's profile
       *
       * @param {string} prop name of a property to unset
       */
      unset(prop) {
        if (!StringHelper.isValid(prop)) {
          StringHelper.raiseError(PARAMS.PROPERTY_NAME);
        }
        this.mixpanelImpl.groupUnsetProperty(
          this.token,
          this.groupKey,
          this.groupID,
          prop
        );
      }

      /**
       * Remove value from a list-valued property only if it is already present in the list.
       * If the property does not currently exist, the remove will be ignored.
       * If the property exists and is not list-valued, the remove will be ignored.
       *
       * @param {string} name the Group Analytics list-valued property that should have a value removed
       * @param {any} value the value that will be removed from the list
       */
      remove(name, value) {
        if (!StringHelper.isValid(name)) {
          StringHelper.raiseError(PARAMS.PROPERTY_NAME);
        }

        this.mixpanelImpl.groupRemovePropertyValue(
          this.token,
          this.groupKey,
          this.groupID,
          name,
          value
        );
      }

      /**
       * Adds values to a list-valued property only if they are not already present in the list.
       * If the property does not currently exist, it will be created with the given list as its value.
       * If the property exists and is not list-valued, the union will be ignored.
       *
       * @param {string} name name of the list-valued property to set or modify
       * @param {array} value an array of values to add to the property value if not already present
       */
      union(name, value) {
        if (!StringHelper.isValid(name)) {
          StringHelper.raiseError(PARAMS.PROPERTY_NAME);
        }
        value = Array.isArray(value) ? value : [value];
        this.mixpanelImpl.groupUnionProperty(
          this.token,
          this.groupKey,
          this.groupID,
          name,
          value
        );
      }
    }

    class Helper {
      /**
          Get the library data from package.json file.
         */
      static getMetaData() {
        let metadata = JSON.parse(JSON.stringify(packageJson.metadata));
        metadata["$lib_version"] = packageJson.version;
        return metadata;
      }

      /**
          Get current device platform.
         */
      static getDevicePlatform() {
        switch (Platform.OS) {
          case "android":
            return DevicePlatform.Android;
          case "ios":
            return DevicePlatform.iOS;
          default:
            return DevicePlatform.Unknown;
        }
      }
    }

    class StringHelper {
      /**
          Check whether the parameter is not a blank string.
         */
      static isValid(str) {
        return typeof str === "string" && !/^\s*$/.test(str);
      }

      /**
          Check whether the parameter is undefined or not a blank string.
         */
      static isValidOrUndefined(str) {
        return str === undefined || StringHelper.isValid(str);
      }

      /**
          Raise a string validation error.
         */
      static raiseError(paramName) {
        throw new Error(`${paramName}${ERROR_MESSAGE.INVALID_STRING}`);
      }
    }

    class ObjectHelper {
      /**
          Check whether the parameter is an object.
         */
      static isValid(obj) {
        return typeof obj === "object";
      }

      /**
          Check whether the parameter is undefined or an object.
         */
      static isValidOrUndefined(obj) {
        return obj === undefined || ObjectHelper.isValid(obj);
      }

      /**
          Raise an object validation error.
         */
      static raiseError(paramName) {
        throw new Error(`${paramName}${ERROR_MESSAGE.INVALID_OBJECT}`);
      }
    }