// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. /// Matches only alphabetic characters. final RegExp alphabeticPattern = RegExp(r'^[A-Za-z]+$'); /// Matches strings that contain alphanumeric characters and underscores. final RegExp alphaNumericPattern = RegExp(r'^[A-Za-z0-9_]+$'); /// Checks that the body of the request being sent to /// GA4 is within the limitations. /// /// Limitations can be found: /// https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#limitations void checkBody(Map body) { // Ensure we have the correct top level keys if (!body.keys.contains('client_id')) { throw AnalyticsException('client_id missing from top level keys'); } if (!body.keys.contains('events')) { throw AnalyticsException('events missing from top level keys'); } if (!body.keys.contains('user_properties')) { throw AnalyticsException('user_properties missing from top level keys'); } final events = body['events'] as List; final userProperties = body['user_properties'] as Map; // GA4 Limitation: // Requests can have a maximum of 25 events if (events.length > 25) { throw AnalyticsException('25 is the max number of events'); } // Checks for each event object for (final eventMap in events.cast>()) { final eventName = eventMap['name'] as String; // GA4 Limitation: // Event names must be 40 characters or fewer, may only contain // alpha-numeric characters and underscores, and must start // with an alphabetic character if (eventName.length > 40) { throw AnalyticsException( 'Limit event names to 40 chars or less\n' 'Event name: "$eventName" is too long', ); } if (!alphaNumericPattern.hasMatch(eventName)) { throw AnalyticsException( 'Event name can only have alphanumeric chars and underscores\n' 'Event name: "$eventName" contains invalid characters', ); } if (!alphabeticPattern.hasMatch(eventName[0])) { throw AnalyticsException( 'Event name first char must be alphabetic char\n' 'Event name: "$eventName" must begin with a valid character', ); } final eventParams = eventMap['params'] as Map; // GA4 Limitation: // Events can have a maximum of 25 parameters if (eventParams.length > 25) { throw AnalyticsException( 'Limit params for each event to less than 25\n' 'Event: "$eventName" has too many parameters', ); } // Loop through each of the event parameters for (final entry in eventParams.entries) { final key = entry.key; final value = entry.value; // GA4 Limitation: // Ensure that each value for the event params is one // of the following types: // `String`, `int`, `double`, or `bool` if (!(value is String || value is int || value is double || value is bool)) { throw AnalyticsException( 'Values for event params have to be String, int, double, or bool\n' 'Value for "$key" is not a valid type for event: "$eventName"', ); } // GA4 Limitation: // Parameter names (including item parameters) must be 40 characters // or fewer, may only contain alpha-numeric characters and underscores, // and must start with an alphabetic character if (key.length > 40) { throw AnalyticsException( 'Limit event param names to 40 chars or less\n' 'The key: "$key" under the event: "$eventName" is too long', ); } if (!alphaNumericPattern.hasMatch(key)) { throw AnalyticsException( 'Event param name can only have alphanumeric chars and underscores\n' 'The key: "$key" under the event: "$eventName" contains ' 'invalid characters', ); } if (!alphabeticPattern.hasMatch(key[0])) { throw AnalyticsException( 'Event param name first char must be alphabetic char\n' 'The key: "$key" under the event: "$eventName" must begin ' 'in a valid character', ); } // GA4 Limitation: // Parameter values (including item parameter values) must be 100 // characters or fewer if (value.runtimeType == String) { value as String; if (value.length > 100) { throw AnalyticsException( 'Limit characters in event param value to 100 chars or less\n' 'Value for "$key" is too long, value="$value"', ); } } } } // GA4 Limitation: // Events can have a maximum of 25 user properties if (userProperties.length > 25) { throw AnalyticsException('Limit user properties to 25 or less'); } // Checks for each user property item for (final entry in userProperties.entries) { final key = entry.key; final value = entry.value as Map; // GA4 Limitation: // User property names must be 24 characters or fewer if (key.length > 24) { throw AnalyticsException('Limit user property names to 24 chars or less\n' 'The user property key: "$key" is too long'); } // GA4 Limitation: // User property values must be 36 characters or fewer final userPropValue = value['value']; if (userPropValue is String && userPropValue.length > 36) { throw AnalyticsException( 'Limit user property values to 36 chars or less\n' 'For the user property key "$key", the value "${value['value']}" ' 'is too long', ); } } } class AnalyticsException implements Exception { final String message; AnalyticsException(this.message); @override String toString() => 'AnalyticsException: $message'; }