@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.
- package/CHANGELOG.md +0 -0
- package/LICENSE +201 -0
- package/lib/.tsbuildinfo +3 -0
- package/lib/InAppMessaging/InAppMessaging.d.ts +55 -0
- package/lib/InAppMessaging/InAppMessaging.js +342 -0
- package/lib/InAppMessaging/InAppMessaging.js.map +1 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/index.d.ts +50 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/index.js +482 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/index.js.map +1 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/types.d.ts +25 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/types.js +11 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/types.js.map +1 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/utils.d.ts +18 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/utils.js +254 -0
- package/lib/InAppMessaging/Providers/AWSPinpointProvider/utils.js.map +1 -0
- package/lib/InAppMessaging/Providers/index.d.ts +1 -0
- package/lib/InAppMessaging/Providers/index.js +7 -0
- package/lib/InAppMessaging/Providers/index.js.map +1 -0
- package/lib/InAppMessaging/SessionTracker/SessionTracker.d.ts +9 -0
- package/lib/InAppMessaging/SessionTracker/SessionTracker.js +68 -0
- package/lib/InAppMessaging/SessionTracker/SessionTracker.js.map +1 -0
- package/lib/InAppMessaging/SessionTracker/SessionTracker.native.d.ts +10 -0
- package/lib/InAppMessaging/SessionTracker/SessionTracker.native.js +53 -0
- package/lib/InAppMessaging/SessionTracker/SessionTracker.native.js.map +1 -0
- package/lib/InAppMessaging/SessionTracker/index.d.ts +3 -0
- package/lib/InAppMessaging/SessionTracker/index.js +8 -0
- package/lib/InAppMessaging/SessionTracker/index.js.map +1 -0
- package/lib/InAppMessaging/SessionTracker/types.d.ts +6 -0
- package/lib/InAppMessaging/SessionTracker/types.js +5 -0
- package/lib/InAppMessaging/SessionTracker/types.js.map +1 -0
- package/lib/InAppMessaging/eventListeners.d.ts +3 -0
- package/lib/InAppMessaging/eventListeners.js +28 -0
- package/lib/InAppMessaging/eventListeners.js.map +1 -0
- package/lib/InAppMessaging/index.d.ts +4 -0
- package/lib/InAppMessaging/index.js +11 -0
- package/lib/InAppMessaging/index.js.map +1 -0
- package/lib/InAppMessaging/types.d.ts +99 -0
- package/lib/InAppMessaging/types.js +10 -0
- package/lib/InAppMessaging/types.js.map +1 -0
- package/lib/Notifications.d.ts +20 -0
- package/lib/Notifications.js +46 -0
- package/lib/Notifications.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +9 -0
- package/lib/index.js.map +1 -0
- package/lib/types.d.ts +7 -0
- package/lib/types.js +3 -0
- package/lib/types.js.map +1 -0
- package/lib-esm/.tsbuildinfo +3 -0
- package/lib-esm/InAppMessaging/InAppMessaging.d.ts +55 -0
- package/lib-esm/InAppMessaging/InAppMessaging.js +340 -0
- package/lib-esm/InAppMessaging/InAppMessaging.js.map +1 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/index.d.ts +50 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/index.js +480 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/index.js.map +1 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/types.d.ts +25 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/types.js +9 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/types.js.map +1 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/utils.d.ts +18 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/utils.js +252 -0
- package/lib-esm/InAppMessaging/Providers/AWSPinpointProvider/utils.js.map +1 -0
- package/lib-esm/InAppMessaging/Providers/index.d.ts +1 -0
- package/lib-esm/InAppMessaging/Providers/index.js +4 -0
- package/lib-esm/InAppMessaging/Providers/index.js.map +1 -0
- package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.d.ts +9 -0
- package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.js +65 -0
- package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.js.map +1 -0
- package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.native.d.ts +10 -0
- package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.native.js +50 -0
- package/lib-esm/InAppMessaging/SessionTracker/SessionTracker.native.js.map +1 -0
- package/lib-esm/InAppMessaging/SessionTracker/index.d.ts +3 -0
- package/lib-esm/InAppMessaging/SessionTracker/index.js +5 -0
- package/lib-esm/InAppMessaging/SessionTracker/index.js.map +1 -0
- package/lib-esm/InAppMessaging/SessionTracker/types.d.ts +6 -0
- package/lib-esm/InAppMessaging/SessionTracker/types.js +3 -0
- package/lib-esm/InAppMessaging/SessionTracker/types.js.map +1 -0
- package/lib-esm/InAppMessaging/eventListeners.d.ts +3 -0
- package/lib-esm/InAppMessaging/eventListeners.js +26 -0
- package/lib-esm/InAppMessaging/eventListeners.js.map +1 -0
- package/lib-esm/InAppMessaging/index.d.ts +4 -0
- package/lib-esm/InAppMessaging/index.js +6 -0
- package/lib-esm/InAppMessaging/index.js.map +1 -0
- package/lib-esm/InAppMessaging/types.d.ts +99 -0
- package/lib-esm/InAppMessaging/types.js +8 -0
- package/lib-esm/InAppMessaging/types.js.map +1 -0
- package/lib-esm/Notifications.d.ts +20 -0
- package/lib-esm/Notifications.js +44 -0
- package/lib-esm/Notifications.js.map +1 -0
- package/lib-esm/index.d.ts +3 -0
- package/lib-esm/index.js +5 -0
- package/lib-esm/index.js.map +1 -0
- package/lib-esm/types.d.ts +7 -0
- package/lib-esm/types.js +1 -0
- package/lib-esm/types.js.map +1 -0
- package/package.json +99 -0
- package/src/InAppMessaging/InAppMessaging.ts +330 -0
- package/src/InAppMessaging/Providers/AWSPinpointProvider/index.ts +522 -0
- package/src/InAppMessaging/Providers/AWSPinpointProvider/types.ts +38 -0
- package/src/InAppMessaging/Providers/AWSPinpointProvider/utils.ts +329 -0
- package/src/InAppMessaging/Providers/index.ts +4 -0
- package/src/InAppMessaging/SessionTracker/SessionTracker.native.ts +60 -0
- package/src/InAppMessaging/SessionTracker/SessionTracker.ts +78 -0
- package/src/InAppMessaging/SessionTracker/index.ts +6 -0
- package/src/InAppMessaging/SessionTracker/types.ts +11 -0
- package/src/InAppMessaging/eventListeners.ts +42 -0
- package/src/InAppMessaging/index.ts +20 -0
- package/src/InAppMessaging/types.ts +146 -0
- package/src/Notifications.ts +49 -0
- package/src/index.ts +20 -0
- 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
|
+
}
|