From 08c3eb9479016b474655a978b95b5654531228e3 Mon Sep 17 00:00:00 2001 From: Tyler-Larkin Date: Mon, 29 Jul 2024 05:40:07 -0700 Subject: [PATCH 1/7] fix(aws): moved StoppableTimer to a shared packaged --- .../lib/src/util}/stoppable_timer.dart | 0 .../lib/src/analytics_plugin_impl.dart | 1 - packages/aws_common/lib/aws_common.dart | 1 + .../lib/src/util/stoppable_timer.dart | 44 +++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) rename packages/{analytics/amplify_analytics_pinpoint_dart/lib/src/impl/analytics_client => amplify_core/lib/src/util}/stoppable_timer.dart (100%) create mode 100644 packages/aws_common/lib/src/util/stoppable_timer.dart diff --git a/packages/analytics/amplify_analytics_pinpoint_dart/lib/src/impl/analytics_client/stoppable_timer.dart b/packages/amplify_core/lib/src/util/stoppable_timer.dart similarity index 100% rename from packages/analytics/amplify_analytics_pinpoint_dart/lib/src/impl/analytics_client/stoppable_timer.dart rename to packages/amplify_core/lib/src/util/stoppable_timer.dart diff --git a/packages/analytics/amplify_analytics_pinpoint_dart/lib/src/analytics_plugin_impl.dart b/packages/analytics/amplify_analytics_pinpoint_dart/lib/src/analytics_plugin_impl.dart index f981412729..a7187214a3 100644 --- a/packages/analytics/amplify_analytics_pinpoint_dart/lib/src/analytics_plugin_impl.dart +++ b/packages/analytics/amplify_analytics_pinpoint_dart/lib/src/analytics_plugin_impl.dart @@ -9,7 +9,6 @@ import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/endpoi import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_client/event_client.dart'; import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_client/queued_item_store/dart_queued_item_store.dart'; import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/session_manager.dart'; -import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/stoppable_timer.dart'; import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/app_lifecycle_provider.dart'; import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/cached_events_path_provider.dart'; import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/device_context_info_provider.dart'; diff --git a/packages/aws_common/lib/aws_common.dart b/packages/aws_common/lib/aws_common.dart index 899c1e6dc4..8d7555d1a1 100644 --- a/packages/aws_common/lib/aws_common.dart +++ b/packages/aws_common/lib/aws_common.dart @@ -52,5 +52,6 @@ export 'src/util/json.dart'; export 'src/util/print.dart'; export 'src/util/recase.dart'; export 'src/util/serializable.dart'; +export 'src/util/stoppable_timer.dart'; export 'src/util/stream.dart'; export 'src/util/uuid.dart'; diff --git a/packages/aws_common/lib/src/util/stoppable_timer.dart b/packages/aws_common/lib/src/util/stoppable_timer.dart new file mode 100644 index 0000000000..8e57cf9f49 --- /dev/null +++ b/packages/aws_common/lib/src/util/stoppable_timer.dart @@ -0,0 +1,44 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:async'; + +import 'package:meta/meta.dart'; + +/// {@template amplify_analytics_pinpoint_dart.stoppable_timer} +/// A Timer that can be stopped and started again. +/// {@endtemplate} +@protected +class StoppableTimer { + /// {@macro amplify_analytics_pinpoint_dart.stoppable_timer} + /// + /// The [callback] is invoked repeatedly with [duration] intervals until + /// stopped with the [stop] function. + StoppableTimer({ + required this.duration, + required Future Function() callback, + required void Function(Object) onError, + }) : _callback = callback, + _timer = Timer.periodic(duration, (Timer t) { + callback().catchError((Object e) { + onError(e); + }); + }); + Timer _timer; + + /// [Duration] between invocations of the provided callback function. + @visibleForTesting + final Duration duration; + final void Function() _callback; + + /// Start the timer. + void start() { + if (_timer.isActive) return; + _timer = Timer.periodic(duration, (Timer t) => _callback()); + } + + /// Stop the timer. + void stop() { + _timer.cancel(); + } +} From acc79aab2372157e5a85f294c69ab5cb2f13a4e0 Mon Sep 17 00:00:00 2001 From: Tyler-Larkin Date: Mon, 29 Jul 2024 05:41:11 -0700 Subject: [PATCH 2/7] fix(push): Added a timer to flush push notification events --- .../lib/src/pinpoint_provider.dart | 22 ++++++++++++++++ .../test/pinpoint_provider_test.dart | 26 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart b/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart index 6cf94d6b6f..7f686a7ea8 100644 --- a/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart +++ b/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart @@ -27,12 +27,18 @@ final AmplifyLogger _logger = AmplifyLogger.category(Category.pushNotifications) /// [init] method has to be called before other methods can be used. /// Once initialized, it can [registerDevice], [recordNotificationEvent] /// & [identifyUser] with Pinpoint. +/// +/// To release any initialized resources [dispose] should be called. /// {@endtemplate} class PinpointProvider implements ServiceProviderClient { /// {@macro amplify_push_notifications_pinpoint.pinpoint_provider} late AnalyticsClient _analyticsClient; + /// Periodic timer for flushing events made public for testing + @visibleForTesting + late final StoppableTimer autoEventSubmitter; + static const _androidCampaignIdKey = 'pinpoint.campaign.campaign_id'; static const _androidCampaignActivityIdKey = 'pinpoint.campaign.campaign_activity_id'; @@ -91,6 +97,12 @@ class PinpointProvider implements ServiceProviderClient { region: region, authProvider: authProvider, ); + + autoEventSubmitter = StoppableTimer( + duration: const Duration(seconds: 10), + callback: _flushEvents, + onError: (e) => _logger.warn('Exception in events auto flush', e), + ); _isInitialized = true; } @@ -104,6 +116,10 @@ class PinpointProvider implements ServiceProviderClient { } } + Future _flushEvents() { + return _analyticsClient.eventClient.flushEvents(); + } + @override Future identifyUser({ required String userId, @@ -274,4 +290,10 @@ class PinpointProvider implements ServiceProviderClient { return ChannelType.apns; } } + + /// Cleans up and releases resources retained by this object. + /// This includes but is not limited to periodic timers for flushing events. + void dispose() { + autoEventSubmitter.stop(); + } } diff --git a/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart b/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart index ff72d427ee..a70e5dd1e1 100644 --- a/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart +++ b/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart @@ -197,6 +197,32 @@ void main() { ); }); + test('flush events timer initialized', () async { + when( + () => mockAmplifyAuthProviderRepository.getAuthProvider( + APIAuthorizationType.iam.authProviderToken, + ), + ).thenReturn(awsIamAmplifyAuthProvider); + when( + () => mockAnalyticsClient.init( + pinpointAppId: any(named: 'pinpointAppId'), + region: any(named: 'region'), + authProvider: any(named: 'authProvider'), + ), + ).thenAnswer((realInvocation) async {}); + + await pinpointProvider.init( + config: notificationsPinpointConfig, + authProviderRepo: mockAmplifyAuthProviderRepository, + analyticsClient: mockAnalyticsClient, + ); + + expect( + pinpointProvider.autoEventSubmitter.duration, + const Duration(seconds: 10), + ); + }); + test('identifyUser should run successfully', () async { when( () => mockAmplifyAuthProviderRepository.getAuthProvider( From dc5c08e80507acae7381b5eb76456a1b740b9e15 Mon Sep 17 00:00:00 2001 From: Tyler-Larkin Date: Mon, 29 Jul 2024 05:41:48 -0700 Subject: [PATCH 3/7] fix(push): Updated event source names to match Pinpoint documentation --- .../src/types/notifications/push/pinpoint_event_source.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart b/packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart index ab80ac7562..b3f56b49af 100644 --- a/packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart +++ b/packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart @@ -11,8 +11,8 @@ /// [Journeys](https://docs.aws.amazon.com/pinpoint/latest/userguide/journeys.html) /// {@endtemplate} enum PinpointEventSource { - campaign('campaign'), - journey('journey'); + campaign('_campaign'), + journey('_journey'); const PinpointEventSource(this.name); From 26f65647abb1df2c2f402a773f1a9bccab70a79d Mon Sep 17 00:00:00 2001 From: Tyler-Larkin Date: Mon, 29 Jul 2024 07:35:28 -0700 Subject: [PATCH 4/7] chore(notifications): fixed spacing issue --- .../lib/src/pinpoint_provider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart b/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart index 7f686a7ea8..e766ad998b 100644 --- a/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart +++ b/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart @@ -97,7 +97,7 @@ class PinpointProvider implements ServiceProviderClient { region: region, authProvider: authProvider, ); - + autoEventSubmitter = StoppableTimer( duration: const Duration(seconds: 10), callback: _flushEvents, From 1ff5395cefd7eb76ebea9a3fdeadc3abcbdaf783 Mon Sep 17 00:00:00 2001 From: Tyler-Larkin Date: Wed, 31 Jul 2024 09:32:50 -0700 Subject: [PATCH 5/7] chore(notification): Made PinpointEventTypeSource private and deprecated PinpointEventSource --- .../push/pinpoint_event_source.dart | 6 +++-- .../lib/src/pinpoint_event_type_source.dart | 26 +++++++++++++++++++ .../lib/src/pinpoint_provider.dart | 9 ++++--- .../test/pinpoint_provider_test.dart | 7 ++--- 4 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_event_type_source.dart diff --git a/packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart b/packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart index b3f56b49af..ed60871846 100644 --- a/packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart +++ b/packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart @@ -10,10 +10,12 @@ /// [Campaigns](https://docs.aws.amazon.com/pinpoint/latest/userguide/campaigns.html) /// [Journeys](https://docs.aws.amazon.com/pinpoint/latest/userguide/journeys.html) /// {@endtemplate} +@Deprecated('this enum will be private in the next major version') enum PinpointEventSource { - campaign('_campaign'), - journey('_journey'); + campaign('campaign'), + journey('journey'); + @Deprecated('this enum will be private in the next major version') const PinpointEventSource(this.name); final String name; diff --git a/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_event_type_source.dart b/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_event_type_source.dart new file mode 100644 index 0000000000..95667966b5 --- /dev/null +++ b/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_event_type_source.dart @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +/// {@template amplify_core.push.pinpoint_event_source} +/// The source of a push notification. +/// +/// Pinpoint offers two ways of sending push notifications to users campaigns and journeys. +/// +/// See also: +/// [Campaigns](https://docs.aws.amazon.com/pinpoint/latest/userguide/campaigns.html) +/// [Journeys](https://docs.aws.amazon.com/pinpoint/latest/userguide/journeys.html) +/// {@endtemplate} +enum PinpointEventTypeSource { + /// [campaign] represents a push notification originating from a campaign + /// [Campaign Events](https://docs.aws.amazon.com/pinpoint/latest/developerguide/event-streams-data-campaign.html) + campaign('_campaign'), + + /// [journey] represents a push notification originating from a journey + /// [Journey Events](https://docs.aws.amazon.com/pinpoint/latest/developerguide/event-streams-data-journey.html) + journey('_journey'); + + const PinpointEventTypeSource(this.name); + + /// [name] contains the source prefix for event_type attributes + final String name; +} diff --git a/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart b/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart index e766ad998b..081f213417 100644 --- a/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart +++ b/packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart @@ -15,6 +15,7 @@ import 'package:amplify_core/amplify_core.dart'; // ignore: implementation_imports import 'package:amplify_core/src/config/amplify_outputs/notifications/notifications_outputs.dart'; import 'package:amplify_push_notifications_pinpoint/src/event_info_type.dart'; +import 'package:amplify_push_notifications_pinpoint/src/pinpoint_event_type_source.dart'; import 'package:amplify_secure_storage/amplify_secure_storage.dart'; import 'package:flutter/widgets.dart'; @@ -225,14 +226,14 @@ class PinpointProvider implements ServiceProviderClient { }) { final data = notification.data; final analyticsProperties = CustomProperties(); - var source = PinpointEventSource.campaign.name; + var source = PinpointEventTypeSource.campaign.name; var campaign = {}; var journey = {}; var pinpointData = {}; // Android payload contain pinpoint.campaign.* format if (data.containsKey(_androidCampaignIdKey)) { - source = PinpointEventSource.campaign.name; + source = PinpointEventTypeSource.campaign.name; campaign['campaign_id'] = data[_androidCampaignIdKey] as String; if (data.containsKey(_androidCampaignActivityIdKey)) { campaign['campaign_activity_id'] = @@ -255,7 +256,7 @@ class PinpointProvider implements ServiceProviderClient { // iOS payload conatin a nested map of pinpoint, campaign, * format if (pinpointData.containsKey('campaign')) { - source = PinpointEventSource.campaign.name; + source = PinpointEventTypeSource.campaign.name; campaign = Map.from( pinpointData['campaign'] as Map, ); @@ -263,7 +264,7 @@ class PinpointProvider implements ServiceProviderClient { // Common way of represting journeys both on Android and iOS payloads if (pinpointData.containsKey('journey')) { - source = PinpointEventSource.journey.name; + source = PinpointEventTypeSource.journey.name; journey = Map.from( pinpointData['journey'] as Map, ); diff --git a/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart b/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart index a70e5dd1e1..21f0fd4643 100644 --- a/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart +++ b/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart @@ -8,6 +8,7 @@ import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_ import 'package:amplify_core/src/config/amplify_outputs/notifications/amazon_pinpoint_channel.dart'; import 'package:amplify_core/src/config/amplify_outputs/notifications/notifications_outputs.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_push_notifications_pinpoint/src/pinpoint_event_type_source.dart'; import 'package:amplify_push_notifications_pinpoint/src/pinpoint_provider.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -142,7 +143,7 @@ void main() { final properties = res.properties; final source = res.source; expect(properties.attributes.containsKey('journey_id'), isTrue); - expect(source, equals(PinpointEventSource.journey.name)); + expect(source, equals( PinpointEventTypeSource.journey.name)); }); test( @@ -154,7 +155,7 @@ void main() { final properties = res.properties; final source = res.source; expect(properties.attributes.containsKey('campaign_id'), isTrue); - expect(source, equals(PinpointEventSource.campaign.name)); + expect(source, equals(PinpointEventTypeSource.campaign.name)); }); }); @@ -434,7 +435,7 @@ void main() { verify( () => mockEventClient.recordEvent( eventType: - '${PinpointEventSource.campaign.name}.${PinpointEventType.foregroundMessageReceived.name}', + '${PinpointEventTypeSource.campaign.name}.${PinpointEventType.foregroundMessageReceived.name}', properties: any(named: 'properties'), ), ); From ab3dee1bb0bd23bef8080c270d18fe24d97bb2e1 Mon Sep 17 00:00:00 2001 From: Tyler-Larkin Date: Thu, 1 Aug 2024 08:52:05 -0700 Subject: [PATCH 6/7] chore(notifications): formatted file --- .../test/pinpoint_provider_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart b/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart index 21f0fd4643..1cf3ce0eb0 100644 --- a/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart +++ b/packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart @@ -143,7 +143,7 @@ void main() { final properties = res.properties; final source = res.source; expect(properties.attributes.containsKey('journey_id'), isTrue); - expect(source, equals( PinpointEventTypeSource.journey.name)); + expect(source, equals(PinpointEventTypeSource.journey.name)); }); test( From 7efaa1034e54e08b7ecc0e28a3792e11b2ba49d8 Mon Sep 17 00:00:00 2001 From: Tyler-Larkin Date: Wed, 14 Aug 2024 08:48:52 -0700 Subject: [PATCH 7/7] chore(notifications): Removed extra StoppableTiemr Class --- .../lib/src/util/stoppable_timer.dart | 43 ------------------- 1 file changed, 43 deletions(-) delete mode 100644 packages/amplify_core/lib/src/util/stoppable_timer.dart diff --git a/packages/amplify_core/lib/src/util/stoppable_timer.dart b/packages/amplify_core/lib/src/util/stoppable_timer.dart deleted file mode 100644 index 4de7560050..0000000000 --- a/packages/amplify_core/lib/src/util/stoppable_timer.dart +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'dart:async'; - -import 'package:meta/meta.dart'; - -/// {@template amplify_analytics_pinpoint_dart.stoppable_timer} -/// A Timer that can be stopped and started again. -/// {@endtemplate} -class StoppableTimer { - /// {@macro amplify_analytics_pinpoint_dart.stoppable_timer} - /// - /// The [callback] is invoked repeatedly with [duration] intervals until - /// stopped with the [stop] function. - StoppableTimer({ - required this.duration, - required Future Function() callback, - required void Function(Object) onError, - }) : _callback = callback, - _timer = Timer.periodic(duration, (Timer t) { - callback().catchError((Object e) { - onError(e); - }); - }); - Timer _timer; - - /// [Duration] between invocations of the provided callback function. - @visibleForTesting - final Duration duration; - final void Function() _callback; - - /// Start the timer. - void start() { - if (_timer.isActive) return; - _timer = Timer.periodic(duration, (Timer t) => _callback()); - } - - /// Stop the timer. - void stop() { - _timer.cancel(); - } -}