import AbortController from 'abort-controller' ; import debug from 'debug' ; import fetch from 'node-fetch' ; import os from 'os' ; import qs from 'qs' ; import uuid from 'uuid/v4' ; import * as builtinTaskNames from '../../builtin-tasks/task-names' ; import { isLocalDev } from '../core/execution-mode' ; import { isRunningOnCiServer } from '../util/ci-detection' ; import { readAnalyticsId , readFirstLegacyAnalyticsId , readSecondLegacyAnalyticsId , writeAnalyticsId } from '../util/global-dir' ; import { getPackageJson } from '../util/packageInfo' ; const log = debug ( 'redspot:core:analytics' ) ; interface RawAnalytics { v : '1' ; tid : string ; cid : string ; dp : string ; dh : string ; t : string ; ua : string ; cs : string ; cm : string ; cd1 : string ; cd2 : string ; cd3 : string ; } type AbortAnalytics = ( ) => void ; const googleAnalyticsUrl = 'https://www.google-analytics.com/collect' ; export class Analytics { public static async getInstance ( telemetryConsent ) { const analytics = new Analytics ( { clientId : await getClientId ( ) , telemetryConsent , userType : getUserType ( ) } ) ; return analytics ; } private readonly _clientId : string ; private readonly _enabled : boolean ; private readonly _userType : string ; private readonly _trackingId : string = 'UA-117668706-3' ; private constructor ( { clientId , telemetryConsent , userType } ) { this . _clientId = clientId ; this . _enabled = ! isLocalDev ( ) && ! isRunningOnCiServer ( ) && telemetryConsent === true ; this . _userType = userType ; } param taskName returns public async sendTaskHit ( taskName ) { if ( this . _isABuiltinTaskName ( taskName ) ) { taskName = 'builtin' ; } else { taskName = 'custom' ; } if ( ! this . _enabled ) { return [ ( ) => { } , Promise . resolve ( ) ] ; } return this . _sendHit ( await this . _taskHit ( taskName ) ) ; } private _isABuiltinTaskName ( taskName ) { return Object . values < string > ( builtinTaskNames ) . includes ( taskName ) ; } private async _taskHit ( taskName ) { return { v : '1' , t : 'pageview' , tid : this . _trackingId , cid : this . _clientId , dp : `/task/${ taskName }` , dh : 'cli.redspot.org' , ua : getUserAgent ( ) , cs : this . _userType , cm : 'User Type' , cd1 : 'redspot-project' , cd2 : this . _userType , cd3 : await getRedspotVersion ( ) } ; } private _sendHit ( hit ) { log ( `Sending hit for ${ hit . dp }` ) ; const controller = new AbortController ( ) ; const abortAnalytics = ( ) => { log ( `Aborting hit for ${ JSON . stringify ( hit . dp ) }` ) ; controller . abort ( ) ; } ; const hitPayload = qs . stringify ( hit ) ; log ( `Hit payload: ${ JSON . stringify ( hit ) }` ) ; const hitPromise = fetch ( googleAnalyticsUrl , { body : hitPayload , method : 'POST' , signal : controller . signal } ) . then ( ( ) => { log ( `Hit for ${ JSON . stringify ( hit . dp ) } sent successfully` ) ; } ) . catch ( ( ) => { log ( 'Hit request failed' ) ; } ) ; return [ abortAnalytics , hitPromise ] ; } } async function getClientId ( ) { let clientId = await readAnalyticsId ( ) ; if ( clientId === undefined ) { clientId = ( await readSecondLegacyAnalyticsId ( ) ) ?? ( await readFirstLegacyAnalyticsId ( ) ) ; if ( clientId === undefined ) { log ( 'Client Id not found, generating a new one' ) ; clientId = uuid ( ) ; } await writeAnalyticsId ( clientId ) ; } return clientId ; } function getUserType ( ) { return isRunningOnCiServer ( ) ? 'CI' : 'Developer' ; } /**
 * At the moment, we couldn't find a reliably way to report the OS () in Node,
 * as the versions reported by the various `os` APIs (`os.platform()`, `os.type()`, etc)
 * return values different to those expected by Google Analytics
 * We decided to take the compromise of just reporting the OS Platform (OSX/Linux/Windows) for now (version information is bogus for now).
 */ function getOperatingSystem ( ) { switch ( os . type ( ) ) { case 'Windows_NT' : return '(Windows NT 6.1; Win64; x64)' ; case 'Darwin' : return '(Macintosh; Intel Mac OS X 10_13_6)' ; case 'Linux' : return '(X11; Linux x86_64)' ; default : return '(Unknown)' ; } } function getUserAgent ( ) { return `Node/${ process . version } ${ getOperatingSystem ( ) }` ; } async function getRedspotVersion ( ) { const { version } = await getPackageJson ( ) ; return `Redspot ${ version }` ; }