| 
									
										
										
										
											2017-01-17 16:27:08 -08:00
										 |  |  | import _ from 'lodash'; | 
					
						
							|  |  |  | import debug from 'debug'; | 
					
						
							|  |  |  | import dedent from 'dedent'; | 
					
						
							|  |  |  | import fs from 'fs'; | 
					
						
							| 
									
										
										
										
											2019-08-13 21:42:58 +05:30
										 |  |  | import { google } from 'googleapis'; | 
					
						
							| 
									
										
										
										
											2017-01-17 16:27:08 -08:00
										 |  |  | import { Observable } from 'rx'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { timeCache, observeMethod } from './rx'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // one million!
 | 
					
						
							|  |  |  | const upperBound = 1000 * 1000; | 
					
						
							|  |  |  | const scope = 'https://www.googleapis.com/auth/analytics.readonly'; | 
					
						
							|  |  |  | const pathToCred = process.env.GOOGLE_APPLICATION_CREDENTIALS; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const log = debug('fcc:server:utils:about'); | 
					
						
							| 
									
										
										
										
											2019-08-13 21:42:58 +05:30
										 |  |  | const analytics = google.analytics('v3'); | 
					
						
							| 
									
										
										
										
											2017-01-17 16:27:08 -08:00
										 |  |  | const makeRequest = observeMethod(analytics.data.realtime, 'get'); | 
					
						
							|  |  |  | export const toBoundInt = _.flow( | 
					
						
							|  |  |  |   // first convert string to integer
 | 
					
						
							|  |  |  |   _.toInteger, | 
					
						
							|  |  |  |   // then we bound the integer to prevent weird things like Infinity
 | 
					
						
							|  |  |  |   // and negative numbers
 | 
					
						
							|  |  |  |   // can't wait to the day we need to update this!
 | 
					
						
							|  |  |  |   _.partialRight(_.clamp, 0, upperBound) | 
					
						
							|  |  |  | ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function createActiveUsers() { | 
					
						
							|  |  |  |   const zero = Observable.of(0); | 
					
						
							|  |  |  |   let credentials; | 
					
						
							|  |  |  |   if (!pathToCred) { | 
					
						
							|  |  |  |     // if no path to credentials set to zero;
 | 
					
						
							|  |  |  |     log(dedent`
 | 
					
						
							|  |  |  |       no google applications credentials environmental variable found | 
					
						
							|  |  |  |       'GOOGLE_APPLICATION_CREDENTIALS' | 
					
						
							|  |  |  |       'activeUser' api will always return 0 | 
					
						
							|  |  |  |       this can safely be ignored during development | 
					
						
							|  |  |  |     `);
 | 
					
						
							|  |  |  |     return zero; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     credentials = require(fs.realpathSync(pathToCred)); | 
					
						
							|  |  |  |   } catch (err) { | 
					
						
							|  |  |  |     log('google applications credentials file failed to require'); | 
					
						
							|  |  |  |     console.error(err); | 
					
						
							|  |  |  |     // if we can't require credentials set to zero;
 | 
					
						
							|  |  |  |     return zero; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if ( | 
					
						
							|  |  |  |     !credentials.private_key || | 
					
						
							|  |  |  |     !credentials.client_email || | 
					
						
							|  |  |  |     !credentials.viewId | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     log(dedent`
 | 
					
						
							|  |  |  |       google applications credentials json should have a | 
					
						
							|  |  |  |       * private_key | 
					
						
							|  |  |  |       * client_email | 
					
						
							|  |  |  |       * viewId | 
					
						
							|  |  |  |       but none were found | 
					
						
							|  |  |  |     `);
 | 
					
						
							|  |  |  |     return zero; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-13 21:42:58 +05:30
										 |  |  |   const client = new google.auth.JWT( | 
					
						
							| 
									
										
										
										
											2017-01-17 16:27:08 -08:00
										 |  |  |     credentials['client_email'], | 
					
						
							|  |  |  |     null, | 
					
						
							|  |  |  |     credentials['private_key'], | 
					
						
							| 
									
										
										
										
											2019-02-18 19:32:49 +00:00
										 |  |  |     [scope] | 
					
						
							| 
									
										
										
										
											2017-01-17 16:27:08 -08:00
										 |  |  |   ); | 
					
						
							|  |  |  |   const authorize = observeMethod(client, 'authorize'); | 
					
						
							|  |  |  |   const options = { | 
					
						
							|  |  |  |     ids: `ga:${credentials.viewId}`, | 
					
						
							|  |  |  |     auth: client, | 
					
						
							|  |  |  |     metrics: 'rt:activeUsers' | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   return Observable.defer( | 
					
						
							|  |  |  |     // we wait for authorize to complete before attempting to make request
 | 
					
						
							|  |  |  |     // this ensures our token is initialized and valid
 | 
					
						
							|  |  |  |     // we defer here to make sure the actual request is done per subscription
 | 
					
						
							|  |  |  |     // instead of once at startup
 | 
					
						
							|  |  |  |     () => authorize().flatMap(() => makeRequest(options)) | 
					
						
							|  |  |  |   ) | 
					
						
							|  |  |  |     // data: Array[body|Object, request: Request]
 | 
					
						
							|  |  |  |     .map(data => data[0]) | 
					
						
							|  |  |  |     .map( | 
					
						
							|  |  |  |       ({ totalsForAllResults } = {}) => totalsForAllResults['rt:activeUsers'] | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     .map(toBoundInt) | 
					
						
							|  |  |  |     // print errors to error log for logging, duh
 | 
					
						
							|  |  |  |     .do(null, err => console.error(err)) | 
					
						
							|  |  |  |     // always send a number down
 | 
					
						
							|  |  |  |     .catch(() => Observable.of(0)) | 
					
						
							|  |  |  |     ::timeCache(2, 'seconds'); | 
					
						
							|  |  |  | } |