Skip to content

Commit cf904dc

Browse files
authored
fix: push notification flush events (#5215)
fix: push notification flush events (#5215)
1 parent 221cf61 commit cf904dc

File tree

7 files changed

+87
-8
lines changed

7 files changed

+87
-8
lines changed

packages/amplify_core/lib/src/types/notifications/push/pinpoint_event_source.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
/// [Campaigns](https://docs.aws.amazon.com/pinpoint/latest/userguide/campaigns.html)
1111
/// [Journeys](https://docs.aws.amazon.com/pinpoint/latest/userguide/journeys.html)
1212
/// {@endtemplate}
13+
@Deprecated('this enum will be private in the next major version')
1314
enum PinpointEventSource {
1415
campaign('campaign'),
1516
journey('journey');
1617

18+
@Deprecated('this enum will be private in the next major version')
1719
const PinpointEventSource(this.name);
1820

1921
final String name;

packages/analytics/amplify_analytics_pinpoint_dart/lib/src/analytics_plugin_impl.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/endpoi
99
import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_client/event_client.dart';
1010
import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_client/queued_item_store/dart_queued_item_store.dart';
1111
import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/session_manager.dart';
12-
import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/stoppable_timer.dart';
1312
import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/app_lifecycle_provider.dart';
1413
import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/cached_events_path_provider.dart';
1514
import 'package:amplify_analytics_pinpoint_dart/src/impl/flutter_provider_interfaces/device_context_info_provider.dart';

packages/aws_common/lib/aws_common.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,6 @@ export 'src/util/json.dart';
5252
export 'src/util/print.dart';
5353
export 'src/util/recase.dart';
5454
export 'src/util/serializable.dart';
55+
export 'src/util/stoppable_timer.dart';
5556
export 'src/util/stream.dart';
5657
export 'src/util/uuid.dart';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
88
/// {@template amplify_analytics_pinpoint_dart.stoppable_timer}
99
/// A Timer that can be stopped and started again.
1010
/// {@endtemplate}
11+
@protected
1112
class StoppableTimer {
1213
/// {@macro amplify_analytics_pinpoint_dart.stoppable_timer}
1314
///
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/// {@template amplify_core.push.pinpoint_event_source}
5+
/// The source of a push notification.
6+
///
7+
/// Pinpoint offers two ways of sending push notifications to users campaigns and journeys.
8+
///
9+
/// See also:
10+
/// [Campaigns](https://docs.aws.amazon.com/pinpoint/latest/userguide/campaigns.html)
11+
/// [Journeys](https://docs.aws.amazon.com/pinpoint/latest/userguide/journeys.html)
12+
/// {@endtemplate}
13+
enum PinpointEventTypeSource {
14+
/// [campaign] represents a push notification originating from a campaign
15+
/// [Campaign Events](https://docs.aws.amazon.com/pinpoint/latest/developerguide/event-streams-data-campaign.html)
16+
campaign('_campaign'),
17+
18+
/// [journey] represents a push notification originating from a journey
19+
/// [Journey Events](https://docs.aws.amazon.com/pinpoint/latest/developerguide/event-streams-data-journey.html)
20+
journey('_journey');
21+
22+
const PinpointEventTypeSource(this.name);
23+
24+
/// [name] contains the source prefix for event_type attributes
25+
final String name;
26+
}

packages/notifications/push/amplify_push_notifications_pinpoint/lib/src/pinpoint_provider.dart

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'package:amplify_core/amplify_core.dart';
1515
// ignore: implementation_imports
1616
import 'package:amplify_core/src/config/amplify_outputs/notifications/notifications_outputs.dart';
1717
import 'package:amplify_push_notifications_pinpoint/src/event_info_type.dart';
18+
import 'package:amplify_push_notifications_pinpoint/src/pinpoint_event_type_source.dart';
1819
import 'package:amplify_secure_storage/amplify_secure_storage.dart';
1920
import 'package:flutter/widgets.dart';
2021

@@ -27,12 +28,18 @@ final AmplifyLogger _logger = AmplifyLogger.category(Category.pushNotifications)
2728
/// [init] method has to be called before other methods can be used.
2829
/// Once initialized, it can [registerDevice], [recordNotificationEvent]
2930
/// & [identifyUser] with Pinpoint.
31+
///
32+
/// To release any initialized resources [dispose] should be called.
3033
/// {@endtemplate}
3134
class PinpointProvider implements ServiceProviderClient {
3235
/// {@macro amplify_push_notifications_pinpoint.pinpoint_provider}
3336
3437
late AnalyticsClient _analyticsClient;
3538

39+
/// Periodic timer for flushing events made public for testing
40+
@visibleForTesting
41+
late final StoppableTimer autoEventSubmitter;
42+
3643
static const _androidCampaignIdKey = 'pinpoint.campaign.campaign_id';
3744
static const _androidCampaignActivityIdKey =
3845
'pinpoint.campaign.campaign_activity_id';
@@ -92,6 +99,12 @@ class PinpointProvider implements ServiceProviderClient {
9299
authProvider: authProvider,
93100
);
94101

102+
autoEventSubmitter = StoppableTimer(
103+
duration: const Duration(seconds: 10),
104+
callback: _flushEvents,
105+
onError: (e) => _logger.warn('Exception in events auto flush', e),
106+
);
107+
95108
_isInitialized = true;
96109
}
97110
} on Exception catch (e) {
@@ -104,6 +117,10 @@ class PinpointProvider implements ServiceProviderClient {
104117
}
105118
}
106119

120+
Future<void> _flushEvents() {
121+
return _analyticsClient.eventClient.flushEvents();
122+
}
123+
107124
@override
108125
Future<void> identifyUser({
109126
required String userId,
@@ -209,14 +226,14 @@ class PinpointProvider implements ServiceProviderClient {
209226
}) {
210227
final data = notification.data;
211228
final analyticsProperties = CustomProperties();
212-
var source = PinpointEventSource.campaign.name;
229+
var source = PinpointEventTypeSource.campaign.name;
213230
var campaign = <String, String>{};
214231
var journey = <String, String>{};
215232
var pinpointData = <Object?, Object?>{};
216233

217234
// Android payload contain pinpoint.campaign.* format
218235
if (data.containsKey(_androidCampaignIdKey)) {
219-
source = PinpointEventSource.campaign.name;
236+
source = PinpointEventTypeSource.campaign.name;
220237
campaign['campaign_id'] = data[_androidCampaignIdKey] as String;
221238
if (data.containsKey(_androidCampaignActivityIdKey)) {
222239
campaign['campaign_activity_id'] =
@@ -239,15 +256,15 @@ class PinpointProvider implements ServiceProviderClient {
239256

240257
// iOS payload conatin a nested map of pinpoint, campaign, * format
241258
if (pinpointData.containsKey('campaign')) {
242-
source = PinpointEventSource.campaign.name;
259+
source = PinpointEventTypeSource.campaign.name;
243260
campaign = Map<String, String>.from(
244261
pinpointData['campaign'] as Map<Object?, Object?>,
245262
);
246263
}
247264

248265
// Common way of represting journeys both on Android and iOS payloads
249266
if (pinpointData.containsKey('journey')) {
250-
source = PinpointEventSource.journey.name;
267+
source = PinpointEventTypeSource.journey.name;
251268
journey = Map<String, String>.from(
252269
pinpointData['journey'] as Map<Object?, Object?>,
253270
);
@@ -274,4 +291,10 @@ class PinpointProvider implements ServiceProviderClient {
274291
return ChannelType.apns;
275292
}
276293
}
294+
295+
/// Cleans up and releases resources retained by this object.
296+
/// This includes but is not limited to periodic timers for flushing events.
297+
void dispose() {
298+
autoEventSubmitter.stop();
299+
}
277300
}

packages/notifications/push/amplify_push_notifications_pinpoint/test/pinpoint_provider_test.dart

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:amplify_analytics_pinpoint_dart/src/impl/analytics_client/event_
88
import 'package:amplify_core/src/config/amplify_outputs/notifications/amazon_pinpoint_channel.dart';
99
import 'package:amplify_core/src/config/amplify_outputs/notifications/notifications_outputs.dart';
1010
import 'package:amplify_flutter/amplify_flutter.dart';
11+
import 'package:amplify_push_notifications_pinpoint/src/pinpoint_event_type_source.dart';
1112
import 'package:amplify_push_notifications_pinpoint/src/pinpoint_provider.dart';
1213
import 'package:flutter_test/flutter_test.dart';
1314
import 'package:mocktail/mocktail.dart';
@@ -142,7 +143,7 @@ void main() {
142143
final properties = res.properties;
143144
final source = res.source;
144145
expect(properties.attributes.containsKey('journey_id'), isTrue);
145-
expect(source, equals(PinpointEventSource.journey.name));
146+
expect(source, equals(PinpointEventTypeSource.journey.name));
146147
});
147148

148149
test(
@@ -154,7 +155,7 @@ void main() {
154155
final properties = res.properties;
155156
final source = res.source;
156157
expect(properties.attributes.containsKey('campaign_id'), isTrue);
157-
expect(source, equals(PinpointEventSource.campaign.name));
158+
expect(source, equals(PinpointEventTypeSource.campaign.name));
158159
});
159160
});
160161

@@ -197,6 +198,32 @@ void main() {
197198
);
198199
});
199200

201+
test('flush events timer initialized', () async {
202+
when(
203+
() => mockAmplifyAuthProviderRepository.getAuthProvider(
204+
APIAuthorizationType.iam.authProviderToken,
205+
),
206+
).thenReturn(awsIamAmplifyAuthProvider);
207+
when(
208+
() => mockAnalyticsClient.init(
209+
pinpointAppId: any(named: 'pinpointAppId'),
210+
region: any(named: 'region'),
211+
authProvider: any(named: 'authProvider'),
212+
),
213+
).thenAnswer((realInvocation) async {});
214+
215+
await pinpointProvider.init(
216+
config: notificationsPinpointConfig,
217+
authProviderRepo: mockAmplifyAuthProviderRepository,
218+
analyticsClient: mockAnalyticsClient,
219+
);
220+
221+
expect(
222+
pinpointProvider.autoEventSubmitter.duration,
223+
const Duration(seconds: 10),
224+
);
225+
});
226+
200227
test('identifyUser should run successfully', () async {
201228
when(
202229
() => mockAmplifyAuthProviderRepository.getAuthProvider(
@@ -408,7 +435,7 @@ void main() {
408435
verify(
409436
() => mockEventClient.recordEvent(
410437
eventType:
411-
'${PinpointEventSource.campaign.name}.${PinpointEventType.foregroundMessageReceived.name}',
438+
'${PinpointEventTypeSource.campaign.name}.${PinpointEventType.foregroundMessageReceived.name}',
412439
properties: any(named: 'properties'),
413440
),
414441
);

0 commit comments

Comments
 (0)