UNPKG

10.4 kBPlain TextView Raw
1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4import {
5 Amplify,
6 ConsoleLogger as Logger,
7 Hub,
8 parseAWSExports,
9} from '@aws-amplify/core';
10import { AWSPinpointProvider } from './Providers/AWSPinpointProvider';
11
12import {
13 AnalyticsProvider,
14 EventAttributes,
15 EventMetrics,
16 AnalyticsEvent,
17 AutoTrackSessionOpts,
18 AutoTrackPageViewOpts,
19 AutoTrackEventOpts,
20 PersonalizeAnalyticsEvent,
21 KinesisAnalyticsEvent,
22} from './types';
23import { PageViewTracker, EventTracker, SessionTracker } from './trackers';
24
25const logger = new Logger('AnalyticsClass');
26
27const AMPLIFY_SYMBOL = (
28 typeof Symbol !== 'undefined' && typeof Symbol.for === 'function'
29 ? Symbol.for('amplify_default')
30 : '@@amplify_default'
31) as Symbol;
32
33const dispatchAnalyticsEvent = (event: string, data: any, message: string) => {
34 Hub.dispatch(
35 'analytics',
36 { event, data, message },
37 'Analytics',
38 AMPLIFY_SYMBOL
39 );
40};
41
42const trackers = {
43 pageView: PageViewTracker,
44 event: EventTracker,
45 session: SessionTracker,
46};
47
48type TrackerTypes = keyof typeof trackers;
49type Trackers = typeof trackers[TrackerTypes];
50let _instance = null;
51
52/**
53 * Provide mobile analytics client functions
54 */
55export class AnalyticsClass {
56 private _config;
57 private _pluggables: AnalyticsProvider[];
58 private _disabled: boolean;
59 private _trackers: Trackers | {};
60
61 /**
62 * Initialize Analtyics
63 * @param config - Configuration of the Analytics
64 */
65 constructor() {
66 this._config = {};
67 this._pluggables = [];
68 this._disabled = false;
69 this._trackers = {};
70 _instance = this;
71
72 this.record = this.record.bind(this);
73 Hub.listen('auth', listener);
74 Hub.listen('storage', listener);
75 Hub.listen('analytics', listener);
76 Hub.listen('core', listener);
77 }
78
79 public getModuleName() {
80 return 'Analytics';
81 }
82 /**
83 * configure Analytics
84 * @param {Object} config - Configuration of the Analytics
85 */
86 public configure(config?) {
87 if (!config) return this._config;
88 logger.debug('configure Analytics', config);
89 const amplifyConfig = parseAWSExports(config);
90 this._config = Object.assign(
91 {},
92 this._config,
93 amplifyConfig.Analytics,
94 config
95 );
96
97 if (this._config['disabled']) {
98 this._disabled = true;
99 }
100
101 // turn on the autoSessionRecord if not specified
102 if (this._config['autoSessionRecord'] === undefined) {
103 this._config['autoSessionRecord'] = true;
104 }
105
106 this._pluggables.forEach(pluggable => {
107 // for backward compatibility
108 const providerConfig =
109 pluggable.getProviderName() === 'AWSPinpoint' &&
110 !this._config['AWSPinpoint']
111 ? this._config
112 : this._config[pluggable.getProviderName()];
113
114 pluggable.configure({
115 disabled: this._config['disabled'],
116 autoSessionRecord: this._config['autoSessionRecord'],
117 ...providerConfig,
118 });
119 });
120
121 if (this._pluggables.length === 0) {
122 this.addPluggable(new AWSPinpointProvider());
123 }
124
125 dispatchAnalyticsEvent(
126 'configured',
127 null,
128 `The Analytics category has been configured successfully`
129 );
130 logger.debug('current configuration', this._config);
131 return this._config;
132 }
133
134 /**
135 * add plugin into Analytics category
136 * @param pluggable - an instance of the plugin
137 */
138 public addPluggable(pluggable: AnalyticsProvider) {
139 if (pluggable && pluggable.getCategory() === 'Analytics') {
140 this._pluggables.push(pluggable);
141 // for backward compatibility
142 const providerConfig =
143 pluggable.getProviderName() === 'AWSPinpoint' &&
144 !this._config['AWSPinpoint']
145 ? this._config
146 : this._config[pluggable.getProviderName()];
147 const config = { disabled: this._config['disabled'], ...providerConfig };
148 pluggable.configure(config);
149 return config;
150 }
151 }
152
153 /**
154 * Get the plugin object
155 * @param providerName - the name of the provider to be removed
156 */
157 public getPluggable(providerName: string): AnalyticsProvider {
158 for (let i = 0; i < this._pluggables.length; i += 1) {
159 const pluggable = this._pluggables[i];
160 if (pluggable.getProviderName() === providerName) {
161 return pluggable;
162 }
163 }
164
165 logger.debug('No plugin found with providerName', providerName);
166 return null;
167 }
168
169 /**
170 * Remove the plugin object
171 * @param providerName - the name of the provider to be removed
172 */
173 public removePluggable(providerName: string): void {
174 let idx = 0;
175 while (idx < this._pluggables.length) {
176 if (this._pluggables[idx].getProviderName() === providerName) {
177 break;
178 }
179 idx += 1;
180 }
181
182 if (idx === this._pluggables.length) {
183 logger.debug('No plugin found with providerName', providerName);
184 return;
185 } else {
186 this._pluggables.splice(idx, idx + 1);
187 return;
188 }
189 }
190
191 /**
192 * stop sending events
193 */
194 public disable() {
195 this._disabled = true;
196 }
197
198 /**
199 * start sending events
200 */
201 public enable() {
202 this._disabled = false;
203 }
204
205 /**
206 * Record Session start
207 * @param [provider] - name of the provider.
208 * @return - A promise which resolves if buffer doesn't overflow
209 */
210 public async startSession(provider?: string) {
211 const event = { name: '_session.start' };
212 const params = { event, provider };
213
214 dispatchAnalyticsEvent(
215 'record',
216 event,
217 'Recording Analytics session start event'
218 );
219
220 return this._sendEvent(params);
221 }
222
223 /**
224 * Record Session stop
225 * @param [provider] - name of the provider.
226 * @return - A promise which resolves if buffer doesn't overflow
227 */
228 public async stopSession(provider?: string) {
229 const event = { name: '_session.stop' };
230 const params = { event, provider };
231
232 dispatchAnalyticsEvent(
233 'record',
234 event,
235 'Recording Analytics session stop event'
236 );
237
238 return this._sendEvent(params);
239 }
240
241 /**
242 * Record one analytic event and send it to Pinpoint
243 * @param event - An object with the name of the event, attributes of the event and event metrics.
244 * @param [provider] - name of the provider.
245 */
246 public async record(
247 event: AnalyticsEvent | PersonalizeAnalyticsEvent | KinesisAnalyticsEvent,
248 provider?: string
249 ) {
250 const params = { event, provider };
251
252 dispatchAnalyticsEvent('record', params.event, 'Recording Analytics event');
253
254 return this._sendEvent(params);
255 }
256
257 public async updateEndpoint(
258 attrs: { [key: string]: any },
259 provider?: string
260 ) {
261 const event = { ...attrs, name: '_update_endpoint' };
262
263 return this.record(event, provider);
264 }
265
266 private _sendEvent(params: {
267 event: AnalyticsEvent | PersonalizeAnalyticsEvent | KinesisAnalyticsEvent;
268 provider?: string;
269 }) {
270 if (this._disabled) {
271 logger.debug('Analytics has been disabled');
272 return Promise.resolve();
273 }
274
275 const provider = params.provider ? params.provider : 'AWSPinpoint';
276
277 return new Promise((resolve, reject) => {
278 this._pluggables.forEach(pluggable => {
279 if (pluggable.getProviderName() === provider) {
280 pluggable.record(params, { resolve, reject });
281 }
282 });
283 });
284 }
285
286 /**
287 * Enable or disable auto tracking
288 * @param trackerType - The type of tracker to activate.
289 * @param [opts] - Auto tracking options.
290 */
291 public autoTrack(trackerType: 'session', opts: AutoTrackSessionOpts);
292 public autoTrack(trackerType: 'pageView', opts: AutoTrackPageViewOpts);
293 public autoTrack(trackerType: 'event', opts: AutoTrackEventOpts);
294 // ensures backwards compatibility for non-pinpoint provider users
295 public autoTrack(
296 trackerType: TrackerTypes,
297 opts: { provider: string; [key: string]: any }
298 );
299 public autoTrack(trackerType: TrackerTypes, opts: { [key: string]: any }) {
300 if (!trackers[trackerType]) {
301 logger.debug('invalid tracker type');
302 return;
303 }
304
305 // to sync up two different configuration ways of auto session tracking
306 if (trackerType === 'session') {
307 this._config['autoSessionRecord'] = opts['enable'];
308 }
309
310 const tracker = this._trackers[trackerType];
311 if (!tracker) {
312 this._trackers[trackerType] = new trackers[trackerType](
313 this.record,
314 opts
315 );
316 } else {
317 tracker.configure(opts);
318 }
319 }
320}
321
322let endpointUpdated = false;
323let authConfigured = false;
324let analyticsConfigured = false;
325let credentialsConfigured = false;
326
327const listener = capsule => {
328 const { channel, payload } = capsule;
329 logger.debug('on hub capsule ' + channel, payload);
330
331 switch (channel) {
332 case 'auth':
333 authEvent(payload);
334 break;
335 case 'storage':
336 storageEvent(payload);
337 break;
338 case 'analytics':
339 analyticsEvent(payload);
340 break;
341 case 'core':
342 coreEvent(payload);
343 break;
344 default:
345 break;
346 }
347};
348
349const storageEvent = payload => {
350 const {
351 data: { attrs, metrics },
352 } = payload;
353 if (!attrs) return;
354
355 if (analyticsConfigured) {
356 _instance
357 .record({
358 name: 'Storage',
359 attributes: attrs,
360 metrics,
361 })
362 .catch(e => {
363 logger.debug('Failed to send the storage event automatically', e);
364 });
365 }
366};
367
368const authEvent = payload => {
369 const { event } = payload;
370 if (!event) {
371 return;
372 }
373
374 const recordAuthEvent = async eventName => {
375 if (authConfigured && analyticsConfigured) {
376 try {
377 return await _instance.record({ name: `_userauth.${eventName}` });
378 } catch (err) {
379 logger.debug(
380 `Failed to send the ${eventName} event automatically`,
381 err
382 );
383 }
384 }
385 };
386
387 switch (event) {
388 case 'signIn':
389 return recordAuthEvent('sign_in');
390 case 'signUp':
391 return recordAuthEvent('sign_up');
392 case 'signOut':
393 return recordAuthEvent('sign_out');
394 case 'signIn_failure':
395 return recordAuthEvent('auth_fail');
396 case 'configured':
397 authConfigured = true;
398 if (analyticsConfigured) {
399 sendEvents();
400 }
401 break;
402 }
403};
404
405const analyticsEvent = payload => {
406 const { event } = payload;
407 if (!event) return;
408
409 switch (event) {
410 case 'pinpointProvider_configured':
411 analyticsConfigured = true;
412 if (authConfigured || credentialsConfigured) {
413 sendEvents();
414 }
415 break;
416 }
417};
418
419const coreEvent = payload => {
420 const { event } = payload;
421 if (!event) return;
422
423 switch (event) {
424 case 'credentials_configured':
425 credentialsConfigured = true;
426 if (analyticsConfigured) {
427 sendEvents();
428 }
429 break;
430 }
431};
432
433const sendEvents = () => {
434 const config = _instance.configure();
435 if (!endpointUpdated && config['autoSessionRecord']) {
436 _instance.updateEndpoint({ immediate: true }).catch(e => {
437 logger.debug('Failed to update the endpoint', e);
438 });
439 endpointUpdated = true;
440 }
441 _instance.autoTrack('session', {
442 enable: config['autoSessionRecord'],
443 });
444};
445
446export const Analytics = new AnalyticsClass();
447Amplify.register(Analytics);