| // 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<String, Object?> 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<String, Object?>; | |
| // 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<Map<String, Object?>>()) { | |
| 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<String, Object?>; | |
| // 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<String, Object?>; | |
| // 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'; | |
| } |