@aws-amplify/notifications 0.0.2-unstable.7462

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +0 -0
  2. package/LICENSE +201 -0
  3. package/lib/.tsbuildinfo +3 -0
  4. package/lib/InAppMessaging/InAppMessaging.d.ts +55 -0
  5. package/lib/InAppMessaging/InAppMessaging.js +342 -0
  6. package/lib/InAppMessaging/InAppMessaging.js.map +1 -0
  7. package/lib/InAppMessaging/Providers/AWSPinpointProvider/index.d.ts +50 -0
  8. package/lib/InAppMessaging/Providers/AWSPinpointProvider/index.js +482 -0
  9. package/lib/InAppMessaging/Providers/AWSPinpointProvider/index.js.map +1 -0
  10. package/lib/InAppMessaging/Providers/AWSPinpointProvider/types.d.ts +25 -0
  11. package/lib/InAppMessaging/Providers/AWSPinpointProvider/types.js +11 -0
  12. package/lib/InAppMessaging/Providers/AWSPinpointProvider/types.js.map +1 -0
  13. package/lib/InAppMessaging/Providers/AWSPinpointProvider/utils.d.ts +18 -0
  14. package/lib/InAppMessaging/Providers/AWSPinpointProvider/utils.js +254 -0
  15. package/lib/InAppMessaging/Providers/AWSPinpointProvider/utils.js.map +1 -0
  16. package/lib/InAppMessaging/Providers/index.d.ts +1 -0
  17. package/lib/InAppMessaging/Providers/index.js +7 -0
  18. package/lib/InAppMessaging/Providers/index.js.map +1 -0
  19. package/lib/InAppMessaging/SessionTracker/SessionTracker.d.ts +9 -0
  20. package/lib/InAppMessaging/SessionTracker/SessionTracker.js +68 -0
  21. package/lib/InAppMessaging/SessionTracker/SessionTracker.js.map +1 -0
  22. package/lib/InAppMessaging/SessionTracker/SessionTracker.native.d.ts +10 -0
  23. package/lib/InAppMessaging/SessionTracker/SessionTracker.native.js +53 -0
  24. package/lib/InAppMessaging/SessionTracker/SessionTracker.native.js.map +1 -0
  25. package/lib/InAppMessaging/SessionTracker/index.d.ts +3 -0
  26. package/lib/InAppMessaging/SessionTracker/index.js +8 -0
  27. package/lib/InAppMessaging/SessionTracker/index.js.map +1 -0
  28. package/lib/InAppMessaging/SessionTracker/types.d.ts +6 -0
  29. package/lib/InAppMessaging/SessionTracker/types.js +5 -0
  30. package/lib/InAppMessaging/SessionTracker/types.js.map +1 -0
  31. package/lib/InAppMessaging/eventListeners.d.ts +3 -0
  32. package/lib/InAppMessaging/eventListeners.js +28 -0
  33. package/lib/InAppMessaging/eventListeners.js.map +1 -0
  34. package/lib/InAppMessaging/index.d.ts +4 -0
  35. package/lib/InAppMessaging/index.js +11 -0
  36. package/lib/InAppMessaging/index.js.map +1 -0
  37. package/lib/InAppMessaging/types.d.ts +99 -0
  38. package/lib/InAppMessaging/types.js +10 -0
  39. package/lib/InAppMessaging/types.js.map +1 -0
  40. package/lib/Notifications.d.ts +20 -0
  41. package/lib/Notifications.js +46 -0
  42. package/lib/Notifications.js.map +1 -0
  43. package/lib/index.d.ts +3 -0
  44. package/lib/index.js +9 -0
  45. package/lib/index.js.map +1 -0
  46. package/lib/types.d.ts +7 -0
  47. package/lib/types.js +3 -0
  48. package/lib/types.js.map +1 -0
  49. package/lib-esm/.tsbuildinfo +3 -0
  50. package/lib-esm/InAppMessaging/InAppMessaging.d.ts +55 -0
  51. package/lib-esm/InAppMessaging/InAppMessaging.js +340 -0
  52. package/lib-esm/InAppMessaging/InAppMessaging.js.map +1 -0
  53. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/index.d.ts +50 -0
  54. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/index.js +480 -0
  55. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/index.js.map +1 -0
  56. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/types.d.ts +25 -0
  57. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/types.js +9 -0
  58. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/types.js.map +1 -0
  59. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/utils.d.ts +18 -0
  60. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/utils.js +252 -0
  61. package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/utils.js.map +1 -0
  62. package/lib-esm/InAppMessaging/Providers/index.d.ts +1 -0
  63. package/lib-esm/InAppMessaging/Providers/index.js +4 -0
  64. package/lib-esm/InAppMessaging/Providers/index.js.map +1 -0
  65. package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.d.ts +9 -0
  66. package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.js +65 -0
  67. package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.js.map +1 -0
  68. package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.native.d.ts +10 -0
  69. package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.native.js +50 -0
  70. package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.native.js.map +1 -0
  71. package/lib-esm/InAppMessaging/SessionTracker/index.d.ts +3 -0
  72. package/lib-esm/InAppMessaging/SessionTracker/index.js +5 -0
  73. package/lib-esm/InAppMessaging/SessionTracker/index.js.map +1 -0
  74. package/lib-esm/InAppMessaging/SessionTracker/types.d.ts +6 -0
  75. package/lib-esm/InAppMessaging/SessionTracker/types.js +3 -0
  76. package/lib-esm/InAppMessaging/SessionTracker/types.js.map +1 -0
  77. package/lib-esm/InAppMessaging/eventListeners.d.ts +3 -0
  78. package/lib-esm/InAppMessaging/eventListeners.js +26 -0
  79. package/lib-esm/InAppMessaging/eventListeners.js.map +1 -0
  80. package/lib-esm/InAppMessaging/index.d.ts +4 -0
  81. package/lib-esm/InAppMessaging/index.js +6 -0
  82. package/lib-esm/InAppMessaging/index.js.map +1 -0
  83. package/lib-esm/InAppMessaging/types.d.ts +99 -0
  84. package/lib-esm/InAppMessaging/types.js +8 -0
  85. package/lib-esm/InAppMessaging/types.js.map +1 -0
  86. package/lib-esm/Notifications.d.ts +20 -0
  87. package/lib-esm/Notifications.js +44 -0
  88. package/lib-esm/Notifications.js.map +1 -0
  89. package/lib-esm/index.d.ts +3 -0
  90. package/lib-esm/index.js +5 -0
  91. package/lib-esm/index.js.map +1 -0
  92. package/lib-esm/types.d.ts +7 -0
  93. package/lib-esm/types.js +1 -0
  94. package/lib-esm/types.js.map +1 -0
  95. package/package.json +99 -0
  96. package/src/InAppMessaging/InAppMessaging.ts +330 -0
  97. package/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts +522 -0
  98. package/src/InAppMessaging/Providers/AWSPinpointProvider/types.ts +38 -0
  99. package/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts +329 -0
  100. package/src/InAppMessaging/Providers/index.ts +4 -0
  101. package/src/InAppMessaging/SessionTracker/SessionTracker.native.ts +60 -0
  102. package/src/InAppMessaging/SessionTracker/SessionTracker.ts +78 -0
  103. package/src/InAppMessaging/SessionTracker/index.ts +6 -0
  104. package/src/InAppMessaging/SessionTracker/types.ts +11 -0
  105. package/src/InAppMessaging/eventListeners.ts +42 -0
  106. package/src/InAppMessaging/index.ts +20 -0
  107. package/src/InAppMessaging/types.ts +146 -0
  108. package/src/Notifications.ts +49 -0
  109. package/src/index.ts +20 -0
  110. package/src/types.ts +11 -0
@@ -0,0 +1,522 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import {
5
+ ClientDevice,
6
+ Credentials,
7
+ getAmplifyUserAgent,
8
+ StorageHelper,
9
+ transferKeyToUpperCase,
10
+ } from '@aws-amplify/core';
11
+ import { Cache } from '@aws-amplify/cache';
12
+ import {
13
+ ChannelType,
14
+ GetInAppMessagesCommand,
15
+ GetInAppMessagesCommandInput,
16
+ InAppMessageCampaign as PinpointInAppMessage,
17
+ UpdateEndpointCommand,
18
+ UpdateEndpointCommandInput,
19
+ PinpointClient,
20
+ } from '@aws-sdk/client-pinpoint';
21
+ import { v4 as uuid } from 'uuid';
22
+
23
+ import { addMessageInteractionEventListener } from '../../eventListeners';
24
+ import { NotificationsCategory } from '../../../types';
25
+ import SessionTracker, {
26
+ SessionState,
27
+ SessionStateChangeHandler,
28
+ } from '../../SessionTracker';
29
+ import {
30
+ InAppMessage,
31
+ InAppMessageInteractionEvent,
32
+ InAppMessageLayout,
33
+ InAppMessagingEvent,
34
+ InAppMessagingProvider,
35
+ NotificationsSubcategory,
36
+ UserInfo,
37
+ } from '../../types';
38
+ import {
39
+ AWSPinpointMessageEvent,
40
+ AWSPinpointUserInfo,
41
+ DailyInAppMessageCounter,
42
+ InAppMessageCountMap,
43
+ InAppMessageCounts,
44
+ } from './types';
45
+ import {
46
+ clearMemo,
47
+ dispatchInAppMessagingEvent,
48
+ extractContent,
49
+ extractMetadata,
50
+ getStartOfDay,
51
+ interpretLayout,
52
+ isBeforeEndDate,
53
+ logger,
54
+ matchesAttributes,
55
+ matchesEventType,
56
+ matchesMetrics,
57
+ recordAnalyticsEvent,
58
+ } from './utils';
59
+
60
+ const MESSAGE_DAILY_COUNT_KEY = 'pinpointProvider_inAppMessages_dailyCount';
61
+ const MESSAGE_TOTAL_COUNT_KEY = 'pinpointProvider_inAppMessages_totalCount';
62
+
63
+ export default class AWSPinpointProvider implements InAppMessagingProvider {
64
+ static category: NotificationsCategory = 'Notifications';
65
+ static subCategory: NotificationsSubcategory = 'InAppMessaging';
66
+ static providerName = 'AWSPinpoint';
67
+
68
+ private clientInfo;
69
+ private config: Record<string, any> = {};
70
+ private configured = false;
71
+ private endpointInitialized = false;
72
+ private initialized = false;
73
+ private sessionMessageCountMap: InAppMessageCountMap;
74
+ private sessionTracker: SessionTracker;
75
+
76
+ constructor() {
77
+ this.sessionMessageCountMap = {};
78
+ this.config = {
79
+ storage: new StorageHelper().getStorage(),
80
+ };
81
+ this.clientInfo = ClientDevice.clientInfo() ?? {};
82
+ }
83
+
84
+ /**
85
+ * get the category of the plugin
86
+ */
87
+ getCategory() {
88
+ return AWSPinpointProvider.category;
89
+ }
90
+
91
+ /**
92
+ * get the sub-category of the plugin
93
+ */
94
+ getSubCategory() {
95
+ return AWSPinpointProvider.subCategory;
96
+ }
97
+
98
+ /**
99
+ * get provider name of the plugin
100
+ */
101
+ getProviderName(): string {
102
+ return AWSPinpointProvider.providerName;
103
+ }
104
+
105
+ configure = (config = {}): object => {
106
+ this.config = { ...this.config, ...config };
107
+
108
+ logger.debug('configure AWSPinpointProvider', this.config);
109
+
110
+ // some configuration steps should not be re-run even if provider is re-configured for some reason
111
+ if (!this.configured) {
112
+ this.sessionTracker = new SessionTracker(this.sessionStateChangeHandler);
113
+ this.sessionTracker.start();
114
+ // wire up default Pinpoint message event handling
115
+ addMessageInteractionEventListener((message: InAppMessage) => {
116
+ this.recordMessageEvent(
117
+ message,
118
+ AWSPinpointMessageEvent.MESSAGE_DISPLAYED
119
+ );
120
+ }, InAppMessageInteractionEvent.MESSAGE_DISPLAYED);
121
+ addMessageInteractionEventListener((message: InAppMessage) => {
122
+ this.recordMessageEvent(
123
+ message,
124
+ AWSPinpointMessageEvent.MESSAGE_DISMISSED
125
+ );
126
+ }, InAppMessageInteractionEvent.MESSAGE_DISMISSED);
127
+ addMessageInteractionEventListener((message: InAppMessage) => {
128
+ this.recordMessageEvent(
129
+ message,
130
+ AWSPinpointMessageEvent.MESSAGE_ACTION_TAKEN
131
+ );
132
+ }, InAppMessageInteractionEvent.MESSAGE_ACTION_TAKEN);
133
+ }
134
+
135
+ this.configured = true;
136
+ dispatchInAppMessagingEvent('pinpointProvider_configured', null);
137
+ return this.config;
138
+ };
139
+
140
+ getInAppMessages = async () => {
141
+ if (!this.initialized) {
142
+ await this.init();
143
+ }
144
+ // There is no way to granuarly reconcile the filter memoization as the keys are composited from a message id and
145
+ // event properties thus opting to just clear them out when getting messages rather than leave potentially
146
+ // obsolete entries that will no longer serve any purpose.
147
+ clearMemo();
148
+ try {
149
+ await this.updateEndpoint();
150
+ const { appId, endpointId, pinpointClient } = this.config;
151
+ const input: GetInAppMessagesCommandInput = {
152
+ ApplicationId: appId,
153
+ EndpointId: endpointId,
154
+ };
155
+ const command: GetInAppMessagesCommand = new GetInAppMessagesCommand(
156
+ input
157
+ );
158
+ logger.debug('getting in-app messages', input);
159
+ const response = await pinpointClient.send(command);
160
+ const { InAppMessageCampaigns: messages } =
161
+ response.InAppMessagesResponse;
162
+ dispatchInAppMessagingEvent('getInAppMessages', messages);
163
+ return messages;
164
+ } catch (err) {
165
+ logger.error('Error getting in-app messages', err);
166
+ throw err;
167
+ }
168
+ };
169
+
170
+ processInAppMessages = async (
171
+ messages: any[],
172
+ event: InAppMessagingEvent
173
+ ): Promise<InAppMessage[]> => {
174
+ if (!this.initialized) {
175
+ await this.init();
176
+ }
177
+ let highestPrioritySeen;
178
+ return this.normalizeMessages(
179
+ (messages as PinpointInAppMessage[]).reduce((acc, message) => {
180
+ const messageQualifies =
181
+ matchesEventType(message, event) &&
182
+ matchesAttributes(message, event) &&
183
+ matchesMetrics(message, event) &&
184
+ isBeforeEndDate(message) &&
185
+ this.isBelowCap(message);
186
+ // filter all qualifying messages returning only those that are of (relative) highest priority
187
+ if (messageQualifies) {
188
+ // have not yet encountered message with priority
189
+ if (!highestPrioritySeen) {
190
+ // this message has priority, so reset the accumulator with this message only
191
+ if (message.Priority) {
192
+ highestPrioritySeen = message.Priority;
193
+ return [message];
194
+ } else {
195
+ // this message also has no priority, so just add this message to accumulator
196
+ acc.push(message);
197
+ }
198
+ // have previously encountered message with priority, so only messages with priority matter now
199
+ } else if (message.Priority) {
200
+ // this message has higher priority (lower number), so reset the accumulator with this message only
201
+ if (message.Priority < highestPrioritySeen) {
202
+ highestPrioritySeen = message.Priority;
203
+ return [message];
204
+ // this message has the same priority, so just add this message to accumulator
205
+ } else if (message.Priority === highestPrioritySeen) {
206
+ acc.push(message);
207
+ }
208
+ }
209
+ }
210
+ return acc;
211
+ }, [])
212
+ );
213
+ };
214
+
215
+ identifyUser = async (userId: string, userInfo: UserInfo): Promise<void> => {
216
+ if (!this.initialized) {
217
+ await this.init();
218
+ }
219
+ try {
220
+ await this.updateEndpoint(userId, userInfo);
221
+ } catch (err) {
222
+ logger.error('Error identifying user', err);
223
+ throw err;
224
+ }
225
+ };
226
+
227
+ private init = async () => {
228
+ const { endpointId, storage } = this.config;
229
+ const providerName = this.getProviderName();
230
+ try {
231
+ // Only run sync() if it's available (i.e. React Native)
232
+ if (typeof storage.sync === 'function') {
233
+ await storage.sync();
234
+ }
235
+ // If an endpoint was not provided via configuration, try to get it from cache
236
+ if (!endpointId) {
237
+ this.config.endpointId = await this.getEndpointId();
238
+ }
239
+ this.initialized = true;
240
+ } catch (err) {
241
+ logger.error(`Failed to initialize ${providerName}`, err);
242
+ }
243
+ };
244
+
245
+ private initPinpointClient = async () => {
246
+ const { appId, credentials, pinpointClient, region } = this.config;
247
+
248
+ if (!appId || !credentials || !region) {
249
+ throw new Error(
250
+ 'One or more of credentials, appId or region is not configured'
251
+ );
252
+ }
253
+
254
+ if (pinpointClient) {
255
+ pinpointClient.destroy();
256
+ }
257
+
258
+ this.config.pinpointClient = new PinpointClient({
259
+ region,
260
+ credentials,
261
+ customUserAgent: getAmplifyUserAgent(),
262
+ });
263
+ };
264
+
265
+ private getEndpointId = async () => {
266
+ const { appId } = this.config;
267
+ // Each Pinpoint channel requires its own Endpoint ID
268
+ const cacheKey = `${this.getSubCategory()}:${this.getProviderName()}:${appId}`;
269
+ // First attempt to retrieve the ID from cache
270
+ const cachedEndpointId = await Cache.getItem(cacheKey);
271
+ // Found in cache, just return it
272
+ if (cachedEndpointId) {
273
+ return cachedEndpointId;
274
+ }
275
+ // Otherwise, generate a new ID and store it in long-lived cache before returning it
276
+ const endpointId = uuid();
277
+ // Set a longer TTL to avoid endpoint id being deleted after the default TTL (3 days)
278
+ // Also set its priority to the highest to reduce its chance of being deleted when cache is full
279
+ const ttl = 1000 * 60 * 60 * 24 * 365 * 100; // 100 years
280
+ const expiration = new Date().getTime() + ttl;
281
+ Cache.setItem(cacheKey, endpointId, {
282
+ expires: expiration,
283
+ priority: 1,
284
+ });
285
+ return endpointId;
286
+ };
287
+
288
+ private updateEndpoint = async (
289
+ userId: string = null,
290
+ userInfo: AWSPinpointUserInfo = null
291
+ ) => {
292
+ const {
293
+ appId,
294
+ credentials,
295
+ endpointId,
296
+ endpointInfo = {},
297
+ pinpointClient,
298
+ } = this.config;
299
+ const currentCredentials = await this.getCredentials();
300
+ // Shallow compare to determine if credentials stored here are outdated
301
+ const credentialsUpdated =
302
+ !credentials ||
303
+ Object.keys(currentCredentials).some(
304
+ key => currentCredentials[key] !== credentials[key]
305
+ );
306
+ // If endpoint is already initialized, and nothing else is changing, just early return
307
+ if (
308
+ this.endpointInitialized &&
309
+ !credentialsUpdated &&
310
+ !userId &&
311
+ !userInfo
312
+ ) {
313
+ return;
314
+ }
315
+ // Update credentials
316
+ this.config.credentials = currentCredentials;
317
+ try {
318
+ // Initialize a new pinpoint client if one isn't already configured or if credentials changed
319
+ if (!pinpointClient || credentialsUpdated) {
320
+ await this.initPinpointClient();
321
+ }
322
+ const { address, attributes, demographic, location, metrics, optOut } =
323
+ userInfo ?? {};
324
+ const { appVersion, make, model, platform, version } = this.clientInfo;
325
+ // Create the UpdateEndpoint input, prioritizing passed in user info and falling back to
326
+ // defaults (if any) obtained from the config
327
+ const input: UpdateEndpointCommandInput = {
328
+ ApplicationId: appId,
329
+ EndpointId: endpointId,
330
+ EndpointRequest: {
331
+ RequestId: uuid(),
332
+ EffectiveDate: new Date().toISOString(),
333
+ ChannelType: ChannelType.IN_APP,
334
+ Address: address ?? endpointInfo.address,
335
+ Attributes: {
336
+ ...endpointInfo.attributes,
337
+ ...attributes,
338
+ },
339
+ Demographic: {
340
+ AppVersion: appVersion,
341
+ Make: make,
342
+ Model: model,
343
+ ModelVersion: version,
344
+ Platform: platform,
345
+ ...transferKeyToUpperCase({
346
+ ...endpointInfo.demographic,
347
+ ...demographic,
348
+ }),
349
+ },
350
+ Location: transferKeyToUpperCase({
351
+ ...endpointInfo.location,
352
+ ...location,
353
+ }),
354
+ Metrics: {
355
+ ...endpointInfo.metrics,
356
+ ...metrics,
357
+ },
358
+ OptOut: optOut ?? endpointInfo.optOut,
359
+ User: {
360
+ UserId:
361
+ userId ?? endpointInfo.userId ?? currentCredentials.identityId,
362
+ UserAttributes: attributes ?? endpointInfo.userAttributes,
363
+ },
364
+ },
365
+ };
366
+ const command: UpdateEndpointCommand = new UpdateEndpointCommand(input);
367
+ logger.debug('updating endpoint', input);
368
+ await this.config.pinpointClient.send(command);
369
+ this.endpointInitialized = true;
370
+ } catch (err) {
371
+ throw err;
372
+ }
373
+ };
374
+
375
+ private getCredentials = async () => {
376
+ try {
377
+ const credentials = await Credentials.get();
378
+ if (!credentials) {
379
+ logger.debug('no credentials found');
380
+ return null;
381
+ }
382
+ return Credentials.shear(credentials);
383
+ } catch (err) {
384
+ logger.error('Error getting credentials:', err);
385
+ return null;
386
+ }
387
+ };
388
+
389
+ private sessionStateChangeHandler: SessionStateChangeHandler = (
390
+ state: SessionState
391
+ ) => {
392
+ if (state === 'started') {
393
+ // reset all session counts
394
+ this.sessionMessageCountMap = {};
395
+ }
396
+ };
397
+
398
+ private isBelowCap = ({
399
+ CampaignId,
400
+ SessionCap,
401
+ DailyCap,
402
+ TotalCap,
403
+ }: PinpointInAppMessage): boolean => {
404
+ const { sessionCount, dailyCount, totalCount } =
405
+ this.getMessageCounts(CampaignId);
406
+ return (
407
+ (!SessionCap || sessionCount < SessionCap) &&
408
+ (!DailyCap || dailyCount < DailyCap) &&
409
+ (!TotalCap || totalCount < TotalCap)
410
+ );
411
+ };
412
+
413
+ // Use the current session count in memory or initialize as empty count
414
+ private getSessionCount = (messageId: string): number =>
415
+ this.sessionMessageCountMap[messageId] || 0;
416
+
417
+ private getDailyCount = (): number => {
418
+ const { storage } = this.config;
419
+ const today = getStartOfDay();
420
+ const item = storage.getItem(MESSAGE_DAILY_COUNT_KEY);
421
+ // Parse stored count or initialize as empty count
422
+ const counter: DailyInAppMessageCounter = item
423
+ ? JSON.parse(item)
424
+ : { count: 0, lastCountTimestamp: today };
425
+ // If the stored counter timestamp is today, use it as the count, otherwise reset to 0
426
+ return counter.lastCountTimestamp === today ? counter.count : 0;
427
+ };
428
+
429
+ private getTotalCountMap = (): InAppMessageCountMap => {
430
+ const { storage } = this.config;
431
+ const item = storage.getItem(MESSAGE_TOTAL_COUNT_KEY);
432
+ // Parse stored count map or initialize as empty
433
+ return item ? JSON.parse(item) : {};
434
+ };
435
+
436
+ private getTotalCount = (messageId: string): number => {
437
+ const countMap = this.getTotalCountMap();
438
+ // Return stored count or initialize as empty count
439
+ return countMap[messageId] || 0;
440
+ };
441
+
442
+ private getMessageCounts = (messageId: string): InAppMessageCounts => {
443
+ try {
444
+ return {
445
+ sessionCount: this.getSessionCount(messageId),
446
+ dailyCount: this.getDailyCount(),
447
+ totalCount: this.getTotalCount(messageId),
448
+ };
449
+ } catch (err) {
450
+ logger.error('Failed to get message counts from storage', err);
451
+ }
452
+ };
453
+
454
+ private setSessionCount = (messageId: string, count: number): void => {
455
+ this.sessionMessageCountMap[messageId] = count;
456
+ };
457
+
458
+ private setDailyCount = (count: number): void => {
459
+ const { storage } = this.config;
460
+ const dailyCount: DailyInAppMessageCounter = {
461
+ count,
462
+ lastCountTimestamp: getStartOfDay(),
463
+ };
464
+ try {
465
+ storage.setItem(MESSAGE_DAILY_COUNT_KEY, JSON.stringify(dailyCount));
466
+ } catch (err) {
467
+ logger.error('Failed to save daily message count to storage', err);
468
+ }
469
+ };
470
+
471
+ private setTotalCountMap = (countMap: InAppMessageCountMap): void => {
472
+ const { storage } = this.config;
473
+ try {
474
+ storage.setItem(MESSAGE_TOTAL_COUNT_KEY, JSON.stringify(countMap));
475
+ } catch (err) {
476
+ logger.error('Failed to save total count to storage', err);
477
+ }
478
+ };
479
+
480
+ private setTotalCount = (messageId: string, count: number): void => {
481
+ const updatedMap = {
482
+ ...this.getTotalCountMap(),
483
+ [messageId]: count,
484
+ };
485
+ this.setTotalCountMap(updatedMap);
486
+ };
487
+
488
+ private incrementCounts = async (messageId: string): Promise<void> => {
489
+ const { sessionCount, dailyCount, totalCount } =
490
+ this.getMessageCounts(messageId);
491
+ this.setSessionCount(messageId, sessionCount + 1);
492
+ this.setDailyCount(dailyCount + 1);
493
+ this.setTotalCount(messageId, totalCount + 1);
494
+ };
495
+
496
+ private normalizeMessages = (
497
+ messages: PinpointInAppMessage[]
498
+ ): InAppMessage[] => {
499
+ return messages.map(message => {
500
+ const { CampaignId, InAppMessage } = message;
501
+ return {
502
+ id: CampaignId,
503
+ content: extractContent(message),
504
+ layout: interpretLayout(InAppMessage.Layout),
505
+ metadata: extractMetadata(message),
506
+ };
507
+ });
508
+ };
509
+
510
+ private recordMessageEvent = async (
511
+ message: InAppMessage,
512
+ event: AWSPinpointMessageEvent
513
+ ): Promise<void> => {
514
+ if (!this.initialized) {
515
+ await this.init();
516
+ }
517
+ recordAnalyticsEvent(event, message);
518
+ if (event === AWSPinpointMessageEvent.MESSAGE_DISPLAYED) {
519
+ await this.incrementCounts(message.id);
520
+ }
521
+ };
522
+ }
@@ -0,0 +1,38 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { UserInfo } from '../../types';
5
+
6
+ export type InAppMessageCountMap = Record<string, number>;
7
+
8
+ export type DailyInAppMessageCounter = {
9
+ count: number;
10
+ lastCountTimestamp: string;
11
+ };
12
+
13
+ export type InAppMessageCounts = {
14
+ sessionCount: number;
15
+ dailyCount: number;
16
+ totalCount: number;
17
+ };
18
+
19
+ export type MetricsComparator = (
20
+ metricsVal: number,
21
+ eventVal: number
22
+ ) => boolean;
23
+
24
+ export interface AWSPinpointProviderConfig {
25
+ appId: string;
26
+ region: string;
27
+ }
28
+
29
+ export enum AWSPinpointMessageEvent {
30
+ MESSAGE_DISPLAYED = '_inapp.message_displayed',
31
+ MESSAGE_DISMISSED = '_inapp.message_dismissed',
32
+ MESSAGE_ACTION_TAKEN = '_inapp.message_clicked',
33
+ }
34
+
35
+ export interface AWSPinpointUserInfo extends UserInfo {
36
+ address?: string;
37
+ optOut?: 'ALL' | 'NONE';
38
+ }