Manual Reference Source Test

src/modules/identity-launchdarkly.js

import * as LDClient from 'launchdarkly-js-client-sdk';

import Logger from './logger';

/**
 * Launch Darkly integration
 */
class IdentityLaunchdarkly {
  /**
   * @param {object} config
   * Launch Darkly constructor
   */
  constructor(config = {env: 'production'}) {
    this._logger = new Logger('Identity-LD');
    this._ldclient = null;
    this._ready = false;
    this._user = null;
    this._env = config.env;
    this._onReadyCallbacks = [];

    this.onChange = this.onChange.bind(this);
    this.onReady = this.onReady.bind(this);
  }

  /**
   * Import and initialize library
   *
   * @param {String} projectKey - Identity project key
   * @param {Object} config - Launch Darkly config
   * @param {Object} profile - User profile
   * @param {Function} onChange
   *
   * @return {Promise}
   */
  init(projectKey, config, profile, onChange) {
    return new Promise((resolve, reject) => {
      if (!projectKey || !config) return reject('Project Key and/or Config missing.');
      this._projectKey = projectKey;

      const {
        clientSideID,
        enabled,
      } = config;
      if (!clientSideID || !enabled) return reject('Launch Darkly not initialized.');

      // Set up anonymous user
      const user = this.buildUser(profile);
      this._user = user;

      // Initialize Launch Darkly with user
      this._ldclient = LDClient.initialize(clientSideID, user);

      // Subscribe to change events
      this.subscribe('change', (settings) => {
        this.onChange(settings);
        if (onChange) onChange(settings);
      });

      // Subscribe to ready event
      this.subscribe('ready', () => {
        this.onReady();
        return resolve();
      });
    });
  }

  /**
   * Subscribe to Launch Darkly stream
   *
   * @param {String} name - stream name
   * @param {Function} callback - stream callback
   */
  subscribe(name, callback) {
    this._ldclient.on(name, callback);
  }

  /**
   * Get Launch Darkly flag
   *
   * @param {String} name
   * @return {*}
   */
  getFlag(name) {
    if (!this._ready) return;
    return this._ldclient.variation(name);
  }

  /**
   * Get All Launch Darkly flags
   * @return {*}
   */
  getAllFlags() {
    if (!this._ready) return;
    return this._ldclient.allFlags();
  }

  /**
   * Build user profile object
   *
   * @param {Object} profile
   * @return {Object}
   */
  buildUser(profile) {
    this._logger.log('ldController -- build user profile:', profile);

    let buildResult;

    if (profile && profile.id) {
      buildResult = {
        key: profile.id,
        anonymous: profile.anonymous || false,
        custom: {
          groups: [this._projectKey],
        },
      };

      if (profile.audiences) {
        buildResult.custom.MpAudiences = profile.audiences;
      }
    } else {
      buildResult = {
        key: 'no-key-provided',
        anonymous: true,
        custom: {
          groups: [this._projectKey],
        },
      };
    }

    this._logger.log('ldController -- user build result:', buildResult);

    return buildResult;
  }

  /**
   * Builds the endpoint to hit the conveyor
   * @param {string} mpid
   * @return {string}
   * @private
   */
  _buildConveyorEndpoint(mpid) {
    const leftPart = 'https://';
    const rightPart = '.nbc.com/sdk/api/audience/search?mpid=';
    let endpoint;

    if (this._env === 'production') {
      endpoint = leftPart + 'id' + rightPart + mpid;
    } else {
      endpoint = leftPart + `${this._env}-id` + rightPart + mpid;
    }

    this._logger.log('ldController -- conveyor endpoint:', endpoint);

    return endpoint;
  }

  /**
   * Identifies itself with Launch Darkly sending the audiences of the mParticle
   * user.
   * @param {string | undefined} mpid
   * @return {Promise<Array<string>>}
   */
  _getMParticleUserAudiences(mpid) {
    return new Promise((resolve, reject) => {
      if (!mpid) reject();

      fetch(this._buildConveyorEndpoint(mpid)).then((response) => {
        if (response.ok) {
          return response.json();
        }

        throw new Error('Unable to get MParticle audiences');
      }).then((data) => {
        this._logger.log('ldController -- data when getting audiences:', data);

        resolve(data.audiences.map((audience) => audience.audienceID));
      }).catch(() => {
        this._logger.log('ldController -- failure fetching audiences');

        reject();
      });
    });
  }

  /**
   * Identity new user on Launch Darkly
   *
   * @param {Object} profile
   * @param {Function} cb
   */
  identifyUser(profile, cb) {
    const procedure = () => {
      const newUserDataHandler = (newUser) => {
        if (this._user.key === newUser.key) {
          if (cb) cb(this._user);

          return;
        }

        this._user = newUser;

        this._ldclient.identify(newUser).then(() => {
          if (cb) cb(this._user);
        });
      };

      this._getMParticleUserAudiences((profile && profile.id) || undefined).then((audiences) => {
        const newUser = this.buildUser({...profile, audiences});

        newUserDataHandler(newUser);
      }).catch(() => {
        const newUser = this.buildUser(profile);

        newUserDataHandler(newUser);
      });
    };

    if (this._ready) {
      procedure();
    } else {
      this._addOnReadyCallback(procedure);
    }
  }

  /**
   * React on Launch Darkly change event
   * @param {object} settings
   */
  onChange(settings) {
    this._logger.log('Launch Darkly Change', settings);
  }

  /**
   * React on Launch Darkly ready event
   */
  onReady() {
    this._logger.log('Launch Darkly Ready');
    this._ready = true;

    this._onReadyCallbacks.forEach((callBack) => {
      callBack(this);
    });
  }

  /**
   * Appends a callback to be called when ready
   * @param {Function} cb - CallBacks with 'this' as argument to be called once ready
   */
  _addOnReadyCallback(cb) {
    this._onReadyCallbacks.push(cb);
  }
}

export default IdentityLaunchdarkly;