telemetry.js

import {v4 as createUuid} from 'uuid';

/**
* @classdesc
* Sends and receuives Wiris telemetry data.
*/
export default class TelemetryService {
  constructor() {
    throw new Error('Static class StringManager can not be instantiated.');
  }

  /**
  * Static property.
  * The sender uuid..
  * @private
  * @type {String}
  */
  static get senderId() {
    if (!this._senderId) {

      // Look for previous cookie and use that
      const cookies = document.cookie.split(';').map(cookie => cookie.trim().split('='));
      for (const [ key, value ] of cookies) {
        if (key === senderIdCookieName) {
          this._senderId = value;
          break;
        }
      }

      // No cookie has been previously set
      if (!this._senderId) {
        this._senderId = TelemetryService.composeUUID();
        document.cookie = this.composeCookie(senderIdCookieName, this._senderId, senderIdCookieMaxAge);
      }
    }
    return this._senderId;
  }

  /**
  * Static property.
  * The session uuid.
  * @private
  * @type {String}
  */
  static get sessionId() {
    if (!this._sessionId) {
      this._sessionId = TelemetryService.composeUUID();
    }
    return this._sessionId;
  }

  /**
  * Sends the specified array of messages to the telemetry endpoint.
  */
  static send(messages) {
    const data = {
      method: 'POST',
      cache: 'no-cache',
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': 'CK20op1pOx2LAUjPFP7kB2UPveHZRidG51UJE26m',
        'Accept-Version': '1', // TODO
      },
      body: JSON.stringify(TelemetryService.composeBody(messages)),
    };

    // DEBUG
    // console.log('TelemetryService.send - data:', data);
    return fetch(TelemetryService.endpoint, data)
      .then(response => response)
      .catch((error) => {
        // DEBUG
        // console.error('TelemetryService.send - error:', error);
      });
  }

  /**
   * Composes the 'session' object for the Telemetry Service request call.
   */
  static get session() {
    return {
      id: TelemetryService.sessionId,
      page: 0,
    };
  }

  /**
   * Composes the 'sender' object for the Telemetry Service request call.
   */
  static get sender() {
    return {
      // 1. Client related.
      id: TelemetryService.senderId,
      // This returns 'Linux x86_64'.
      // Not as complete as the example 'Ubuntu; Linux x86_64'.
      os: navigator.oscpu,
      user_agent: window.navigator.userAgent,

      // 2. Server related.
      domain: window.location.hostname,

      // 3. Tech related.
      // The deployment key id as defined on the specification.
      deployment: TelemetryService.deployment,
      // The version of the editor.
      editor_version: (WirisPlugin.currentInstance.environment.editorVersion ? WirisPlugin.currentInstance.environment.editorVersion : ''),
      // The configured language of the editor.
      language: WirisPlugin.currentInstance.language,
      // The version of the current javascript package.
      product_version: WirisPlugin.currentInstance.version,
      backend: (WirisPlugin.currentInstance.serviceProviderProperties.server ? WirisPlugin.currentInstance.serviceProviderProperties.server : ''),

      // TODOs:
      // 1. Backend version, the server language of the service.
      // The possible values are: php, aspx, java or ruby.
      // eslint-disable-next-line max-len
      // backend_version: (WirisPlugin.currentInstance.environment.backend_version ? WirisPlugin.currentInstance.environment.backend_version : ''), // TODO: next iteration.
      // 2. Framework & platform. We can't know this, yet. 
      // This should be injected from the right package.
      // eslint-disable-next-line max-len
      // framework: (WirisPlugin.currentInstance.serviceProviderProperties.framework ? WirisPlugin.currentInstance.serviceProviderProperties.framework : ''), // TODO: next iteration.
      // eslint-disable-next-line max-len
      // platform: (WirisPlugin.currentInstance.serviceProviderProperties.platform ? WirisPlugin.currentInstance.serviceProviderProperties.platform : ''), // TODO: next iteration.
      // 3. Product backend version.
      // product_backend_version: '7.18.0', // TODO: next iteration.
    };
  }

  /**
   * Composes the deployment id key for 'mathtype-web-*'.
   * It follows the convention defined at the 'doc/adr/XXX-telemetry-deployment-name-convention'
   * TODO: create the 'doc/adr'.
   */
  static get deployment() {
    const { editor } = WirisPlugin.currentInstance.environment;

    const prefix = 'mathtype-web-plugin-';
    let suffix = '';

    if (/Generic/.test(editor)) {
      suffix = 'generic';
    } else if (/Froala/.test(editor)) {
      suffix = 'froala';
    } else if (/CKEditor/.test(editor)) {
      suffix = 'ckeditor';
    } else if (/TinyMCE/.test(editor)) {
      suffix = 'tinymce';
    }

    return `${prefix}${suffix}`;
  }

  /**
  * Given 'messages', composes the Telemetry request body as a JSON.
  */
  static composeBody(messages) {
    const body = {
      messages,
      sender: TelemetryService.sender,
      session: TelemetryService.session,
    };
    return body;
  }

  /**
  * Helper function that generates a random UUID used to identify both the batch and the sender.
  * Wikipedia: A universally unique identifier (UUID) is an identifier standard
  * used in software construction. A UUID is simply a 128-bit value.
  * The meaning of each bit is defined by any of several variants. For human-readable display,
  * many systems use a canonical format using hexadecimal text with inserted hyphen characters.
  */
  static composeUUID() {
    return createUuid();
  }

  // TODO: generate an anonymous unique sender footprint based on the local configuration
  static composeSenderUUID() {
    return this.composeUUID();
  }

  static composeCookie(name, content, maxAge) {
    const duration = (maxAge == null) ? '' : `; max-age=${maxAge}`;
    return `${name}=${content}${duration}`;
  }
}

/**
 * Name of the cookie that keeps the sender id.
 */
const senderIdCookieName = 'wiris_telemetry_mathtype_web_senderid';

/**
 * Time in seconds that the cookie with the sender id should last.
 * It's set to 10 years since we'd like to never be deleted.
 */
const senderIdCookieMaxAge = 60 * 60 * 24 * 365 * 10; 

/**
 * TelemetryServer
 * The URL for the telemetry server host is hard-coded.
 */
const telemetryHost = {
  local: 'http://localhost:4000',
  production: 'https://telemetry.wiris.net',
};

/**
 * Stores the Telemetry endpoint host URL.
 * @private
 * @type {String}
 */
TelemetryService.endpoint = telemetryHost.production;

/**
 * Stores the sender uuid.
 * @private
 * @type {String}
 */
TelemetryService._senderId = '';

/**
 * Stores the session uuid.
 * @private
 * @type {String}
 */
TelemetryService._sessionId = '';