All files / src/properties SessionManager.ts

100% Statements 46/46
96.3% Branches 26/27
100% Functions 8/8
100% Lines 45/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138            1x   1x   1x 1x 1x 1x                                                             1x                 23x 23x               1x   12x 12x   12x   9x 9x 9x 9x   12x               54x 28x 2x   28x                 1x 9x 9x 7x 5x     7x                 2x   9x     1x 34x 27x   7x 7x 7x 7x 7x     1x 9x 9x     1x 12x 12x   1x  
/*
* SessionManager.ts
* @author Abhilash Panwar (abpanwar) Hector Hernandez (hectorh)
* @copyright Microsoft 2020
* File containing apis to help manage session.
*/
import { Utils, IExtendedTelemetryItem, EventLatency } from '@microsoft/1ds-core-js';
import StorageManager from './StorageManager';
import { _emptyString, _Extensions, _SessionExtKeys, _EISessionKey, _SignalExtKeys, _UserExtKeys, _UserStateValues } from './Constants';
 
const delimitter = ';';
const extendTime = 1800000; //30 mins
const maxTime = 86400000; //24 hours
const p = parseInt; // for minimization purpose
 
/**
 * Store the session details. <Internal>
 */
interface Session {
  /**
   * Id.
   */
  i: string;
  /**
   * Expiry time.
   */
  e: number;
  /**
   * Start time.
   */
  s: number;
  /**
   * Max expiry time.
   */
  m?: number;
  /**
   * Is user new.
   */
  n?: boolean;
}
 
/**
 * Class to manage session 
 */
export default class SessionManager {
  private _session: Session;
  private _storageManager: StorageManager;
 
  /**
   * Creates an instance of the session manager.
   * @param storageManager storage manager to store session details.
   */
  constructor(storageManager: StorageManager) {
    this._storageManager = storageManager;
    this._session = this._parseSessionString(this._storageManager._getProperty(_EISessionKey));
  }
 
  /**
   * Process the session state based on the recieved event and add the session fields.
   * @param event Event to add session fields to.
   * @param userCookieExists Indicates if the user cookie was created.
   */
  _processEvent(event: IExtendedTelemetryItem, userCookieCreated: boolean, isHit: boolean) {   
      //Add session properties
      event.data[_Extensions._SignalExt][_SignalExtKeys._Session] = {};
      const eventTime = this._parseISOString(event.time);
      //Extend or start session for view events
      if (isHit) {
        //Add latency to make it high
        event.latency = EventLatency.RealTime;
        this._extendOrStartSession(eventTime, event, userCookieCreated);
        event.data[_Extensions._SignalExt][_SignalExtKeys._User][_UserExtKeys._State] = this._session.n ? _UserStateValues._New : _UserStateValues._Returning;    
        event.data[_Extensions._SignalExt][_SignalExtKeys._Session][_SessionExtKeys._Duration] = eventTime - this._session.s;        
      } 
      event.data[_Extensions._SignalExt][_SignalExtKeys._Session][_SessionExtKeys._Id] = this._isSessionActive(eventTime) ? this._session.i : _emptyString;    
  }
 
  /**
   * Returns true if a session is active. False otherwise.
   * @param time The event time for the signal that is used to extend or start the session.
   * @param reload Set to true, if we should reload from storage before checking. For view events this should be true.
   */
  _isSessionActive(time: number, reload = false): boolean {
    if (reload) {
      this._session = this._parseSessionString(this._storageManager._getProperty(_EISessionKey));
    }
    return this._session && Utils.isObject(this._session) && time <= this._session.e;
  }
 
  /**
   * Extends an ongoing session or starts a new one.
   * @param time The event time for the signal that is used to extend or start the session.
   * @param event The event that is being processed.
   * @param userCookieCreated Indicates if the user user cookie is created.
   */
  private _extendOrStartSession(time: number, event: IExtendedTelemetryItem, userCookieCreated: boolean) {
    this._session = this._parseSessionString(this._storageManager._getProperty(_EISessionKey));
    if (!this._session || this._session.e < time) {        
      if (!userCookieCreated) {
        event.data[_Extensions._SignalExt][_SignalExtKeys._User][_UserExtKeys._TimeSinceVisit] = this._session ? time - this._session.e : _emptyString;
      }      
      //start new session      
      this._session = {
        i: Utils.createGuid(),
        s: time,
        e: time + extendTime,
        m: time + maxTime,
        n: userCookieCreated
      };  
    } else {
      //extend existing session
      this._session.e = time + extendTime < this._session.m ? time + extendTime : this._session.m;
    }
    this._storageManager._setProperty(_EISessionKey, this._getSessionString(this._session));
  }
 
  private _parseSessionString(sessionString: string): Session {
    if (!Utils.isString(sessionString)) {
      return null;
    }
    const sessionArr = sessionString.split(delimitter);
    const session: Session = { i: sessionArr[0], s: p(sessionArr[1], 10), e: p(sessionArr[2], 10) };
    session.m = session.s + maxTime;
    session.n = sessionArr.length === 4; // Has new user part of the session.
    return session;
  }
 
  private _getSessionString(session: Session): string {
    var sessionString = session.i + delimitter + session.s + delimitter + session.e;
    return session.n ? sessionString + delimitter + session.n : sessionString;
  }
 
  private _parseISOString(timeString: string): number {
    const b = timeString.split(/\D+/);
    return Date.UTC(p(b[0], 10), p(b[1], 10)-1, p(b[2], 10), p(b[3], 10), p(b[4], 10), p(b[5], 10), p(b[6], 10));
  }
}