1
/*
2
 * Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
5
 * the License. A copy of the License is located at
6
 *
7
 *     http://aws.amazon.com/apache2.0/
8
 *
9
 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
11
 * and limitations under the License.
12
 */
13

14 1
import {
15
	Amplify,
16
	ConsoleLogger as Logger,
17
	Hub,
18
	Parser,
19
} from '@aws-amplify/core';
20 1
import { AWSPinpointProvider } from './Providers/AWSPinpointProvider';
21

22
import {
23
	AnalyticsProvider,
24
	EventAttributes,
25
	EventMetrics,
26
	pageViewTrackOpts,
27
} from './types';
28 1
import { PageViewTracker, EventTracker, SessionTracker } from './trackers';
29

30 1
const logger = new Logger('AnalyticsClass');
31

32 1
const AMPLIFY_SYMBOL = (typeof Symbol !== 'undefined' &&
33
typeof Symbol.for === 'function'
34 1
	? Symbol.for('amplify_default')
35
	: '@@amplify_default') as Symbol;
36

37 1
const dispatchAnalyticsEvent = (event: string, data: any, message: string) => {
38 1
	Hub.dispatch(
39
		'analytics',
40
		{ event, data, message },
41
		'Analytics',
42
		AMPLIFY_SYMBOL
43
	);
44
};
45

46 1
const trackers = {
47
	pageView: PageViewTracker,
48
	event: EventTracker,
49
	session: SessionTracker,
50
};
51

52 1
let _instance = null;
53

54
/**
55
 * Provide mobile analytics client functions
56
 */
57 1
export class AnalyticsClass {
58
	private _config;
59
	private _pluggables: AnalyticsProvider[];
60
	private _disabled;
61
	private _trackers;
62

63
	/**
64
	 * Initialize Analtyics
65
	 * @param config - Configuration of the Analytics
66
	 */
67
	constructor() {
68 1
		this._config = {};
69 1
		this._pluggables = [];
70 1
		this._disabled = false;
71 1
		this._trackers = {};
72 1
		_instance = this;
73

74 1
		this.record = this.record.bind(this);
75 1
		Hub.listen('auth', listener);
76 1
		Hub.listen('storage', listener);
77 1
		Hub.listen('analytics', listener);
78
	}
79

80 1
	public getModuleName() {
81 1
		return 'Analytics';
82
	}
83
	/**
84
	 * configure Analytics
85
	 * @param {Object} config - Configuration of the Analytics
86
	 */
87 1
	public configure(config?) {
88 1
		if (!config) return this._config;
89 1
		logger.debug('configure Analytics', config);
90 1
		const amplifyConfig = Parser.parseMobilehubConfig(config);
91 1
		this._config = Object.assign(
92
			{},
93
			this._config,
94
			amplifyConfig.Analytics,
95
			config
96
		);
97

98 1
		if (this._config['disabled']) {
99 0
			this._disabled = true;
100
		}
101

102 1
		this._pluggables.forEach(pluggable => {
103
			// for backward compatibility
104
			const providerConfig =
105 1
				pluggable.getProviderName() === 'AWSPinpoint' &&
106
				!this._config['AWSPinpoint']
107 1
					? this._config
108
					: this._config[pluggable.getProviderName()];
109

110 1
			pluggable.configure({
111
				disabled: this._config['disabled'],
112
				...providerConfig,
113
			});
114
		});
115

116 1
		if (this._pluggables.length === 0) {
117 1
			this.addPluggable(new AWSPinpointProvider());
118
		}
119

120
		// turn on the autoSessionRecord if not specified
121 1
		if (this._config['autoSessionRecord'] === undefined) {
122 1
			this._config['autoSessionRecord'] = true;
123
		}
124

125 1
		dispatchAnalyticsEvent(
126
			'configured',
127
			null,
128
			`The Analytics category has been configured successfully`
129
		);
130 1
		logger.debug('current configuration', this._config);
131 1
		return this._config;
132
	}
133

134
	/**
135
	 * add plugin into Analytics category
136
	 * @param {Object} pluggable - an instance of the plugin
137
	 */
138 1
	public addPluggable(pluggable: AnalyticsProvider) {
139 1
		if (pluggable && pluggable.getCategory() === 'Analytics') {
140 1
			this._pluggables.push(pluggable);
141
			// for backward compatibility
142
			const providerConfig =
143 1
				pluggable.getProviderName() === 'AWSPinpoint' &&
144
				!this._config['AWSPinpoint']
145 1
					? this._config
146
					: this._config[pluggable.getProviderName()];
147 1
			const config = { disabled: this._config['disabled'], ...providerConfig };
148 1
			pluggable.configure(config);
149 1
			return config;
150
		}
151
	}
152

153
	/**
154
	 * Get the plugin object
155
	 * @param providerName - the name of the plugin
156
	 */
157 1
	public getPluggable(providerName) {
158 1
		for (let i = 0; i < this._pluggables.length; i += 1) {
159 1
			const pluggable = this._pluggables[i];
160 1
			if (pluggable.getProviderName() === providerName) {
161 1
				return pluggable;
162
			}
163
		}
164

165 1
		logger.debug('No plugin found with providerName', providerName);
166 1
		return null;
167
	}
168

169
	/**
170
	 * Remove the plugin object
171
	 * @param providerName - the name of the plugin
172
	 */
173 1
	public removePluggable(providerName) {
174 1
		let idx = 0;
175 1
		while (idx < this._pluggables.length) {
176 1
			if (this._pluggables[idx].getProviderName() === providerName) {
177 0
				break;
178
			}
179 0
			idx += 1;
180
		}
181

182 1
		if (idx === this._pluggables.length) {
183 1
			logger.debug('No plugin found with providerName', providerName);
184 1
			return;
185
		} else {
186 0
			this._pluggables.splice(idx, idx + 1);
187 0
			return;
188
		}
189
	}
190

191
	/**
192
	 * stop sending events
193
	 */
194 1
	public disable() {
195 1
		this._disabled = true;
196
	}
197

198
	/**
199
	 * start sending events
200
	 */
201 1
	public enable() {
202 1
		this._disabled = false;
203
	}
204

205
	/**
206
	 * Record Session start
207
	 * @return - A promise which resolves if buffer doesn't overflow
208
	 */
209 1
	public async startSession(provider?: string) {
210 1
		const params = { event: { name: '_session.start' }, provider };
211 1
		return this._sendEvent(params);
212
	}
213

214
	/**
215
	 * Record Session stop
216
	 * @return - A promise which resolves if buffer doesn't overflow
217
	 */
218 1
	public async stopSession(provider?: string) {
219 1
		const params = { event: { name: '_session.stop' }, provider };
220 1
		return this._sendEvent(params);
221
	}
222

223
	/**
224
	 * Record one analytic event and send it to Pinpoint
225
	 * @param {String} name - The name of the event
226
	 * @param {Object} [attributes] - Attributes of the event
227
	 * @param {Object} [metrics] - Event metrics
228
	 * @return - A promise which resolves if buffer doesn't overflow
229
	 */
230 1
	public async record(
231
		event: string | object,
232
		provider?,
233
		metrics?: EventMetrics
234
	) {
235 1
		let params = null;
236
		// this is just for compatibility, going to be deprecated
237 1
		if (typeof event === 'string') {
238 0
			params = {
239
				event: {
240
					name: event,
241
					attributes: provider,
242
					metrics,
243
				},
244
				provider: 'AWSPinpoint',
245
			};
246
		} else {
247 1
			params = { event, provider };
248
		}
249 1
		return this._sendEvent(params);
250
	}
251

252 1
	public async updateEndpoint(attrs, provider?) {
253 1
		const event = { ...attrs, name: '_update_endpoint' };
254

255 1
		return this.record(event, provider);
256
	}
257

258 1
	private _sendEvent(params) {
259 1
		if (this._disabled) {
260 0
			logger.debug('Analytics has been disabled');
261 0
			return Promise.resolve();
262
		}
263

264 1
		const provider = params.provider ? params.provider : 'AWSPinpoint';
265

266 1
		return new Promise((resolve, reject) => {
267 1
			this._pluggables.forEach(pluggable => {
268 1
				if (pluggable.getProviderName() === provider) {
269 1
					pluggable.record(params, { resolve, reject });
270
				}
271
			});
272
		});
273
	}
274

275 1
	public autoTrack(trackerType, opts) {
276 1
		if (!trackers[trackerType]) {
277 0
			logger.debug('invalid tracker type');
278 0
			return;
279
		}
280

281
		// to sync up two different configuration ways of auto session tracking
282 1
		if (trackerType === 'session') {
283 0
			this._config['autoSessionRecord'] = opts['enable'];
284
		}
285

286 0
		const tracker = this._trackers[trackerType];
287 1
		if (!tracker) {
288 0
			this._trackers[trackerType] = new trackers[trackerType](
289
				this.record,
290
				opts
291
			);
292
		} else {
293 0
			tracker.configure(opts);
294
		}
295
	}
296 1
}
297

298 1
let endpointUpdated = false;
299 1
let authConfigured = false;
300 1
let analyticsConfigured = false;
301 1
const listener = capsule => {
302 1
	const { channel, payload } = capsule;
303 1
	logger.debug('on hub capsule ' + channel, payload);
304

305 1
	switch (channel) {
306 1
		case 'auth':
307 0
			authEvent(payload);
308 0
			break;
309
		case 'storage':
310 0
			storageEvent(payload);
311 0
			break;
312
		case 'analytics':
313 1
			analyticsEvent(payload);
314 1
			break;
315
		default:
316 0
			break;
317
	}
318
};
319

320 1
const storageEvent = payload => {
321
	const {
322 0
		data: { attrs, metrics },
323
	} = payload;
324 1
	if (!attrs) return;
325

326 1
	if (analyticsConfigured) {
327 0
		_instance
328
			.record({
329
				name: 'Storage',
330
				attributes: attrs,
331
				metrics,
332
			})
333
			.catch(e => {
334 0
				logger.debug('Failed to send the storage event automatically', e);
335
			});
336
	}
337
};
338

339 1
const authEvent = payload => {
340 0
	const { event } = payload;
341 1
	if (!event) {
342 0
		return;
343
	}
344

345 0
	const recordAuthEvent = async eventName => {
346 1
		if (authConfigured && analyticsConfigured) {
347
			try {
348 1
				return await _instance.record({ name: `_userauth.${eventName}` });
349
			} catch (err) {
350 0
				logger.debug(
351
					`Failed to send the ${eventName} event automatically`,
352
					err
353
				);
354
			}
355
		}
356
	};
357

358 0
	switch (event) {
359 1
		case 'signIn':
360 0
			return recordAuthEvent('sign_in');
361
		case 'signUp':
362 0
			return recordAuthEvent('sign_up');
363
		case 'signOut':
364 0
			return recordAuthEvent('sign_out');
365
		case 'signIn_failure':
366 0
			return recordAuthEvent('auth_fail');
367
		case 'configured':
368 0
			authConfigured = true;
369 1
			if (authConfigured && analyticsConfigured) {
370 0
				sendEvents();
371
			}
372 0
			break;
373
	}
374
};
375

376 1
const analyticsEvent = payload => {
377 1
	const { event } = payload;
378 1
	if (!event) return;
379

380 1
	switch (event) {
381 1
		case 'pinpointProvider_configured':
382 0
			analyticsConfigured = true;
383 1
			if (authConfigured && analyticsConfigured) {
384 0
				sendEvents();
385
			}
386 0
			break;
387
	}
388
};
389

390 1
const sendEvents = () => {
391 0
	const config = _instance.configure();
392 1
	if (!endpointUpdated && config['autoSessionRecord']) {
393 0
		_instance.updateEndpoint({ immediate: true }).catch(e => {
394 0
			logger.debug('Failed to update the endpoint', e);
395
		});
396 0
		endpointUpdated = true;
397
	}
398 0
	_instance.autoTrack('session', {
399
		enable: config['autoSessionRecord'],
400
	});
401
};
402

403 1
export const Analytics = new AnalyticsClass();
404 1
Amplify.register(Analytics);

Read our documentation on viewing source code .

Loading