1 |
|
2 |
|
3 |
|
4 | import {
|
5 | Amplify,
|
6 | ConsoleLogger as Logger,
|
7 | Hub,
|
8 | parseAWSExports,
|
9 | } from '@aws-amplify/core';
|
10 | import { AWSPinpointProvider } from './Providers/AWSPinpointProvider';
|
11 |
|
12 | import {
|
13 | AnalyticsProvider,
|
14 | EventAttributes,
|
15 | EventMetrics,
|
16 | AnalyticsEvent,
|
17 | AutoTrackSessionOpts,
|
18 | AutoTrackPageViewOpts,
|
19 | AutoTrackEventOpts,
|
20 | PersonalizeAnalyticsEvent,
|
21 | KinesisAnalyticsEvent,
|
22 | } from './types';
|
23 | import { PageViewTracker, EventTracker, SessionTracker } from './trackers';
|
24 |
|
25 | const logger = new Logger('AnalyticsClass');
|
26 |
|
27 | const AMPLIFY_SYMBOL = (
|
28 | typeof Symbol !== 'undefined' && typeof Symbol.for === 'function'
|
29 | ? Symbol.for('amplify_default')
|
30 | : '@@amplify_default'
|
31 | ) as Symbol;
|
32 |
|
33 | const dispatchAnalyticsEvent = (event: string, data: any, message: string) => {
|
34 | Hub.dispatch(
|
35 | 'analytics',
|
36 | { event, data, message },
|
37 | 'Analytics',
|
38 | AMPLIFY_SYMBOL
|
39 | );
|
40 | };
|
41 |
|
42 | const trackers = {
|
43 | pageView: PageViewTracker,
|
44 | event: EventTracker,
|
45 | session: SessionTracker,
|
46 | };
|
47 |
|
48 | type TrackerTypes = keyof typeof trackers;
|
49 | type Trackers = typeof trackers[TrackerTypes];
|
50 | let _instance = null;
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | export class AnalyticsClass {
|
56 | private _config;
|
57 | private _pluggables: AnalyticsProvider[];
|
58 | private _disabled: boolean;
|
59 | private _trackers: Trackers | {};
|
60 |
|
61 | |
62 |
|
63 |
|
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 |
|
84 |
|
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 |
|
102 | if (this._config['autoSessionRecord'] === undefined) {
|
103 | this._config['autoSessionRecord'] = true;
|
104 | }
|
105 |
|
106 | this._pluggables.forEach(pluggable => {
|
107 |
|
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 |
|
136 |
|
137 |
|
138 | public addPluggable(pluggable: AnalyticsProvider) {
|
139 | if (pluggable && pluggable.getCategory() === 'Analytics') {
|
140 | this._pluggables.push(pluggable);
|
141 |
|
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 |
|
155 |
|
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 |
|
171 |
|
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 |
|
193 |
|
194 | public disable() {
|
195 | this._disabled = true;
|
196 | }
|
197 |
|
198 | |
199 |
|
200 |
|
201 | public enable() {
|
202 | this._disabled = false;
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 |
|
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 |
|
225 |
|
226 |
|
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 |
|
243 |
|
244 |
|
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 |
|
288 |
|
289 |
|
290 |
|
291 | public autoTrack(trackerType: 'session', opts: AutoTrackSessionOpts);
|
292 | public autoTrack(trackerType: 'pageView', opts: AutoTrackPageViewOpts);
|
293 | public autoTrack(trackerType: 'event', opts: AutoTrackEventOpts);
|
294 |
|
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 |
|
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 |
|
322 | let endpointUpdated = false;
|
323 | let authConfigured = false;
|
324 | let analyticsConfigured = false;
|
325 | let credentialsConfigured = false;
|
326 |
|
327 | const 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 |
|
349 | const 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 |
|
368 | const 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 |
|
405 | const 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 |
|
419 | const 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 |
|
433 | const 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 |
|
446 | export const Analytics = new AnalyticsClass();
|
447 | Amplify.register(Analytics);
|