96 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			96 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|   | import _ from 'lodash'; | ||
|  | import debug from 'debug'; | ||
|  | import dedent from 'dedent'; | ||
|  | import fs from 'fs'; | ||
|  | import goog from 'googleapis'; | ||
|  | 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'); | ||
|  | const analytics = goog.analytics('v3'); | ||
|  | 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; | ||
|  |   } | ||
|  | 
 | ||
|  |   const client = new goog.auth.JWT( | ||
|  |     credentials['client_email'], | ||
|  |     null, | ||
|  |     credentials['private_key'], | ||
|  |     [scope], | ||
|  |   ); | ||
|  |   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)) | ||
|  |     // cache for 2 seconds to prevent hitting our daily request limit
 | ||
|  |     ::timeCache(2, 'seconds'); | ||
|  | } | ||
|  | 
 |