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;