handleMethodCall method

Future handleMethodCall(
  1. MethodCall call
)

Handles method calls over the MethodChannel of this plugin. Note: Check the "federated" architecture for a new way of doing this: https://flutter.dev/go/federated-plugins

Implementation

Future<dynamic> handleMethodCall(MethodCall call) async {
  final mParticle = JsObject.fromBrowserObject(context['mParticle']);
  final mpIdentity =
      JsObject.fromBrowserObject(context['mParticle']['Identity']);
  final mpCommerce =
      JsObject.fromBrowserObject(context['mParticle']['eCommerce']);
  final mpConsent =
      JsObject.fromBrowserObject(context['mParticle']['Consent']);

  // Calls to the mParticle JS Identity methods are async, so we must await
  // a Future that contains a Completer. The Completer completes inside the JS callback
  // Await-ing this function will return the result from the identity calls, and
  // we return this up to the dart layer as a string.
  Future<String> sendIdentityCall(identityMethod, identityRequest) {
    const String PLATFORM = 'web';
    final completer = Completer<String>();
    String? mpid;
    List<Map<String, String?>>? errors;
    String? previousMpid;
    int? httpCode;

    mpIdentity.callMethod(identityMethod, [
      JsObject.jsify(identityRequest),
      // This callback must parse the httpCode, context, errors, previousMpid,
      // and mpid based on what httpCode is returned. If the httpCode is -1 through
      // -5, it is a client side error, and we format the errors array to include it
      // and set httpCode to null and the message is contained on the result['body'].
      // A 200 http code will have no errors, and all non-200 http codes have errors
      // on the result['body']['error'] object.
      (result) {
        httpCode = result['httpCode'];

        mpid = result?.callMethod('getUser')?.callMethod('getMPID');
        if (identityMethod == 'modify') {
          previousMpid = null;
        } else {
          previousMpid =
              result?.callMethod('getPreviousUser')?.callMethod('getMPID');
        }

        var identityResult = {
          "mpid": mpid,
          "http_code": httpCode,
          "platform": PLATFORM
        };

        switch (httpCode) {
          case 200:
            errors = null;
            identityResult['previous_mpid'] = previousMpid;
            break;
          case 400:
          case 401:
          case 429:
            errors =
                webIdentityHelpers.convertJSErrorArraytoDartErrorList(result);
            break;
          case -1:
          case -2:
          case -3:
          case -4:
          case -5:
            // client side errors for -1 through -5 only have a single error
            errors = [
              {'code': httpCode.toString(), 'message': result['body']}
            ];
            httpCode = null;
            break;
          default:
            // if the http code is 5xx, error will be on result.body.errors
            if (httpCode != null && httpCode as int >= 500) {
              errors = result['body']['errors'];
            } else {
              print(
                  'The httpCode of $httpCode is not covered in the web implementation');
            }
        }

        if (errors != null) {
          identityResult['errors'] = errors;
        }

        completer.complete(jsonEncode(identityResult));
      }
    ]);

    return completer.future;
  }

  switch (call.method) {
    case 'isInitialized':
      try {
        return mParticle.callMethod('getInstance')['_Store']['isInitialized'];
      } catch (error, stackTrace) {
        throw PlatformException(
            code: 'Unimplemented',
            details:
                'Double check your web API key. Unable to get mParticle initialization status.',
            message: 'StackTrace: $stackTrace');
      }

    case 'getAppName':
      return mParticle.callMethod('getAppName');
    case 'logError':
      mParticle.callMethod('logError', [
        call.arguments['eventName'],
        JsObject.jsify(call.arguments['customAttributes']),
      ]);
      break;
    case 'logEvent':
      var shouldUploadBoolean = call.arguments['shouldUploadEvent'];
      if (shouldUploadBoolean != null) {
        mParticle.callMethod('logEvent', [
          call.arguments['eventName'],
          call.arguments['eventType'],
          JsObject.jsify(call.arguments['customAttributes']),
          JsObject.jsify(call.arguments['customFlags']),
          JsObject.jsify(
              {'shouldUploadEvent': call.arguments['shouldUploadEvent']})
        ]);
      } else {
        mParticle.callMethod('logEvent', [
          call.arguments['eventName'],
          call.arguments['eventType'],
          JsObject.jsify(call.arguments['customAttributes']),
          JsObject.jsify(call.arguments['customFlags']),
        ]);
      }

      break;
    case 'logScreenEvent':
      mParticle.callMethod('logPageView', [
        call.arguments['eventName'],
        JsObject.jsify(call.arguments['customAttributes']),
        JsObject.jsify(call.arguments['customFlags']),
      ]);
      break;
    case 'setOptOut':
      mParticle.callMethod('setOptOut', [call.arguments['optOutBoolean']]);
      break;
    //  upload is only required for batching, if a workspace is not set up for batching, it will send events as they come int
    //  to test this, either use a workspace on v3, or override your v3 flag settings as follows:
    //  window.mParticle.config.flags = {"eventsV3":"100","eventBatchingIntervalMillis":"50000"};
    //  simply set eventBatchingIntervalMillis to some arbitrarily large number to ensure you can queue enough events before calling upload
    case 'upload':
      mParticle.callMethod('upload');
      break;

    // user methods
    case 'getMPID':
      return mpIdentity.callMethod('getCurrentUser').callMethod('getMPID');

    case 'getUserAttributes':
      var jsAttributes = mpIdentity.callMethod('getUser',
          [call.arguments["mpid"]]).callMethod('getAllUserAttributes');

      return context['JSON'].callMethod('stringify', [jsAttributes]);

    case 'getUserIdentities':
      // Calling getUserIdentities in the JS SDK returns an object in the shape of:
      // { userIdentities: { customerid: 'customerid1', email: 'email@email.com' } }
      // We must convert this into a dart map with the shape of { 1: 'customerid1', 7: 'email@email.com' }
      // And then return this as a string to the dart layer.

      JsObject jsIdentities = mpIdentity.callMethod('getUser',
          [call.arguments["mpid"]]).callMethod('getUserIdentities');

      // A JsObject needs to be stringified using a web's JSON.stringify, and not jsonEncode
      // which only stringifies dart objects.
      String userIdentitiesString = context['JSON']
          .callMethod('stringify', [jsIdentities['userIdentities']]);

      // Decode the JSON to a dart map. In the example shape above, the keys are 'customerid' and 'email'
      var userIdentitiesMapKeyedByIdentityName =
          jsonDecode(userIdentitiesString);

      // Convert each key of Identity name string to its equivalent stringified Integer
      // In the example shape above, 'customerid' maps to '1', 'email' maps to 7.
      var userIdentitiesMapKeyedByStringifiedNumber = {};
      userIdentitiesMapKeyedByIdentityName.forEach((key, value) {
        userIdentitiesMapKeyedByStringifiedNumber[webIdentityHelpers
            .convertIdentityNameToStringifiedNumber(key)] = value;
      });

      // Return a stringified version of the new dart map
      return jsonEncode(userIdentitiesMapKeyedByStringifiedNumber);

    case 'setUserAttribute':
      mpIdentity.callMethod('getUser', [call.arguments["mpid"]]).callMethod(
          'setUserAttribute',
          [call.arguments["attributeKey"], call.arguments["attributeValue"]]);
      break;
    case 'removeUserAttribute':
      mpIdentity.callMethod('getUser', [call.arguments["mpid"]]).callMethod(
          'removeUserAttribute', [call.arguments["attributeKey"]]);
      break;
    case 'setUserAttributeArray':
      mpIdentity.callMethod('getUser', [call.arguments["mpid"]]).callMethod(
          'setUserAttributeList', [
        call.arguments["attributeKey"],
        JsObject.jsify(call.arguments["attributeValue"])
      ]);
      break;
    case 'setUserTag':
      mpIdentity.callMethod('getUser', [call.arguments["mpid"]]).callMethod(
          'setUserTag', [call.arguments["attributeKey"]]);
      break;
    // identity methods:
    case 'identify':
      var identityRequest = webIdentityHelpers
          .createWebIdentityRequest(call.arguments['identityRequest']);

      var result = await sendIdentityCall('identify', identityRequest);
      return result;
    case 'login':
      var identityRequest = webIdentityHelpers
          .createWebIdentityRequest(call.arguments['identityRequest']);

      var result = await sendIdentityCall('login', identityRequest);
      return result;
    case 'logout':
      var identityRequest = webIdentityHelpers
          .createWebIdentityRequest(call.arguments['identityRequest']);

      var result = await sendIdentityCall('logout', identityRequest);
      return result;
    case 'modify':
      var identityRequest = webIdentityHelpers
          .createWebIdentityRequest(call.arguments['identityRequest']);

      var result = await sendIdentityCall('modify', identityRequest);
      return result;
    case 'aliasUsers':
      var jsAliasRequest = call.arguments["aliasRequest"];
      // If both startTime and endTime exist, then we have a valid aliasRequest
      // and we call aliasUsers
      if (jsAliasRequest['startTime'] != null &&
          jsAliasRequest['endTime'] != null) {
        mpIdentity.callMethod('aliasUsers', [JsObject.jsify(jsAliasRequest)]);
      } else if (jsAliasRequest['startTime'] == null &&
          jsAliasRequest['endTime'] == null) {
        var createdAliasRequest = webIdentityHelpers.createAliasRequest(
            jsAliasRequest, mParticle, mpIdentity);
        // If neither startTime nor endTime exist, we must generate them using
        // the JS SDK
        mpIdentity
            .callMethod('aliasUsers', [JsObject.jsify(createdAliasRequest)]);
      } else {
        print(
            'You included only a startTime or an endTime, but not both. Please include BOTH a startTime and an endTime, or let mParticle calculate both for you');
      }
      break;
    case 'logCommerceEvent':
      var commerceEvent = call.arguments['commerceEvent'];

      var customAttributes = commerceEvent['customAttributes'];
      if (customAttributes == null) {
        customAttributes = {};
      }

      var customFlags = commerceEvent['customFlags'];
      if (customFlags == null) {
        customFlags = {};
      }

      var transactionAttributes = commerceEvent['transactionAttributes'];
      if (transactionAttributes == null) {
        transactionAttributes = {};
      }

      String? checkoutStep = commerceEvent['checkoutStep'];
      if (checkoutStep != null) {
        transactionAttributes['Step'] = checkoutStep;
      }

      String? checkoutOptions = commerceEvent['checkoutOptions'];
      if (checkoutOptions != null) {
        transactionAttributes['Option'] = checkoutOptions;
      }

      String? currency = commerceEvent['currency'];
      if (currency != null) {
        mpCommerce.callMethod('setCurrencyCode', [currency]);
      }

      var eventOptions = {};
      bool? shouldUploadEvent = commerceEvent['shouldUploadEvent'];
      if (shouldUploadEvent != null) {
        eventOptions['shouldUploadEvent'] = shouldUploadEvent;
      }

      List? rawProducts = commerceEvent['products'];
      List? products = [];
      if (rawProducts != null && rawProducts.length > 0) {
        rawProducts.forEach((rawProduct) {
          var product = createJSProduct(mpCommerce, rawProduct);
          products.add(product);
        });
      }

      int? productActionType = commerceEvent['jsProductActionType'];
      int? promotionActionType = commerceEvent['jsPromotionActionType'];

      // log product action
      if (productActionType != null) {
        mpCommerce.callMethod('logProductAction', [
          productActionType,
          JsObject.jsify(products),
          JsObject.jsify(customAttributes),
          JsObject.jsify(customFlags),
          JsObject.jsify(transactionAttributes),
          JsObject.jsify(eventOptions)
        ]);
        return true;
        // log promotion
      } else if (promotionActionType != null) {
        List? rawPromotions = commerceEvent["promotions"];
        List? promotions = [];

        if (rawPromotions != null && rawPromotions.length > 0) {
          rawPromotions.forEach((rawPromotion) {
            var promotion = mpCommerce.callMethod('createPromotion', [
              rawPromotion['promotionId'],
              rawPromotion['creative'],
              rawPromotion['name'],
              rawPromotion['position'],
            ]);
            promotions.add(promotion);
          });
        }

        mpCommerce.callMethod('logPromotion', [
          promotionActionType,
          JsObject.jsify(promotions),
          JsObject.jsify(customAttributes),
          JsObject.jsify(customFlags),
          JsObject.jsify(eventOptions)
        ]);
        return true;
        // log impression
      } else {
        List? rawImpressions = commerceEvent["impressions"];
        List? impressions = [];

        if (rawImpressions != null && rawImpressions.length > 0) {
          rawImpressions.forEach((rawImpression) {
            List impressionProducts = [];
            var rawImpressionProducts = rawImpression['products'];

            if (rawImpressionProducts != null &&
                rawImpressionProducts.length > 0) {
              rawImpressionProducts.forEach((rawImpressionProduct) {
                var product =
                    createJSProduct(mpCommerce, rawImpressionProduct);
                impressionProducts.add(product);
              });
            }
            var impression = mpCommerce.callMethod('createImpression', [
              rawImpression['impressionListName'],
              JsObject.jsify(impressionProducts)
            ]);
            impressions.add(impression);
          });
        }

        mpCommerce.callMethod('logImpression', [
          JsObject.jsify(impressions),
          JsObject.jsify(customAttributes),
          JsObject.jsify(customFlags),
          JsObject.jsify(eventOptions)
        ]);
      }

      break;

    case 'getGDPRConsentState':
      var gdprConsentStateString = '{}';
      var arguments = call.arguments;
      var mpid = arguments['mpid'];
      var user = mpIdentity.callMethod('getUser', [mpid]);
      var consentState = user.callMethod('getConsentState');
      if (consentState != null) {
        var gdprConsentState = consentState.callMethod('getGDPRConsentState');
        if (gdprConsentState != null) {
          gdprConsentStateString =
              context['JSON'].callMethod('stringify', [gdprConsentState]);

          var gdprConsentStateMap = jsonDecode(gdprConsentStateString);
          gdprConsentStateMap.forEach((purpose, value) {
            gdprConsentStateMap[purpose] = {};
            gdprConsentStateMap[purpose]['consented'] = value['Consented'];
            gdprConsentStateMap[purpose]['document'] = value['Document'];
            gdprConsentStateMap[purpose]['location'] = value['Location'];
            gdprConsentStateMap[purpose]['hardwareId'] = value['HardwareId'];
            gdprConsentStateMap[purpose]['timestamp'] = value['Timestamp'];
          });
          gdprConsentStateString = jsonEncode(gdprConsentStateMap);
        }
      }

      return gdprConsentStateString;

    case 'addGDPRConsentState':
      var arguments = call.arguments;
      var consented = arguments['consented'];
      var document = arguments['document'];
      var timestamp = arguments['timestamp'];
      var location = arguments['location'];
      var hardwareId = arguments['hardwareId'];
      var purpose = arguments['purpose'];
      var mpid = arguments['mpid'];
      var gdprConsent = mpConsent.callMethod('createGDPRConsent',
          [consented, timestamp, document, location, hardwareId]);
      var user = mpIdentity.callMethod('getUser', [mpid]);
      var consentState = user.callMethod('getConsentState');
      if (consentState == null) {
        consentState = mpConsent.callMethod('createConsentState');
      }
      consentState.callMethod('addGDPRConsentState', [purpose, gdprConsent]);
      user.callMethod('setConsentState', [consentState]);
      break;

    case 'removeGDPRConsentState':
      var arguments = call.arguments;
      var purpose = arguments['purpose'];
      var mpid = arguments['mpid'];
      var user = mpIdentity.callMethod('getUser', [mpid]);
      var consentState = user.callMethod('getConsentState');
      if (consentState != null) {
        consentState.callMethod('removeGDPRConsentState', [purpose]);
        user.callMethod('setConsentState', [consentState]);
      }
      break;

    case 'getCCPAConsentState':
      var ccpaConsentStateString = '{}';
      var arguments = call.arguments;
      var mpid = arguments['mpid'];
      var user = mpIdentity.callMethod('getUser', [mpid]);
      var consentState = user.callMethod('getConsentState');
      if (consentState != null) {
        var ccpaConsentState = consentState.callMethod('getCCPAConsentState');
        if (ccpaConsentState != null) {
          ccpaConsentStateString =
              context['JSON'].callMethod('stringify', [ccpaConsentState]);
          var ccpaConsentStateMap = jsonDecode(ccpaConsentStateString);

          var ccpaConsentStateForDart = {};
          ccpaConsentStateForDart['consented'] =
              ccpaConsentStateMap['Consented'];
          ccpaConsentStateForDart['document'] =
              ccpaConsentStateMap['Document'];
          ccpaConsentStateForDart['location'] =
              ccpaConsentStateMap['Location'];
          ccpaConsentStateForDart['hardwareId'] =
              ccpaConsentStateMap['HardwareId'];
          ccpaConsentStateForDart['timestamp'] =
              ccpaConsentStateMap['Timestamp'];
          ccpaConsentStateString = jsonEncode(ccpaConsentStateForDart);
        }
      }

      return ccpaConsentStateString;

    case 'addCCPAConsentState':
      var arguments = call.arguments;
      var consented = arguments['consented'];
      var document = arguments['document'];
      var timestamp = arguments['timestamp'];
      var location = arguments['location'];
      var hardwareId = arguments['hardwareId'];
      var mpid = arguments['mpid'];

      var ccpaConsent = mpConsent.callMethod('createCCPAConsent',
          [consented, timestamp, document, location, hardwareId]);

      var user = mpIdentity.callMethod('getUser', [mpid]);
      var consentState = user.callMethod('getConsentState');
      if (consentState == null) {
        consentState = mpConsent.callMethod('createConsentState');
      }
      consentState.callMethod('setCCPAConsentState', [ccpaConsent]);
      user.callMethod('setConsentState', [consentState]);
      break;

    case 'removeCCPAConsentState':
      var arguments = call.arguments;
      var mpid = arguments['mpid'];
      var user = mpIdentity.callMethod('getUser', [mpid]);
      var consentState = user.callMethod('getConsentState');
      if (consentState != null) {
        consentState.callMethod('removeCCPAConsentState');
        user.callMethod('setConsentState', [consentState]);
      }
      break;
    default:
      throw PlatformException(
        code: 'Unimplemented',
        details:
            'mParticle Flutter SDK for Web does not support \'${call.method}\'',
      );
  }
}