purecloud-platform-client-v2
Version:
A JavaScript library to interface with the PureCloud Platform API
1,481 lines (1,320 loc) • 2.72 MB
JavaScript
'use strict';
var axios = require('axios');
var qs = require('qs');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
var PureCloudRegionHosts = {
us_east_1: 'mypurecloud.com',
eu_west_1: 'mypurecloud.ie',
ap_southeast_2: 'mypurecloud.com.au',
ap_northeast_1: 'mypurecloud.jp',
eu_central_1: 'mypurecloud.de',
us_west_2: 'usw2.pure.cloud',
ca_central_1: 'cac1.pure.cloud',
ap_northeast_2: 'apne2.pure.cloud',
eu_west_2: 'euw2.pure.cloud',
ap_south_1: 'aps1.pure.cloud',
us_east_2: 'use2.us-gov-pure.cloud',
sa_east_1: 'sae1.pure.cloud',
me_central_1: 'mec1.pure.cloud',
ap_northeast_3: 'apne3.pure.cloud',
eu_central_2: 'euc2.pure.cloud',
};
class AbstractHttpClient {
constructor() {
this.timeout = 16000;
}
setTimeout(timeout) {
if (timeout === null || timeout === undefined || typeof timeout !== 'number') {
throw new Error("The 'timeout' property must be a number");
}
this.timeout = timeout;
}
setHttpsAgent(httpsAgent) {
if (httpsAgent && typeof httpsAgent !== 'object') {
throw new Error("The 'httpsAgent' property must be an object");
}
this.httpsAgent = httpsAgent;
}
request(httpRequestOptions) {
throw new Error("method must be implemented");
}
enableHooks() {
throw new Error("method must be implemented");
}
/**
* Set a PreHook function that modifies the request config before execution.
* @param {(config: object) => object | Promise<object> | void} hookFunction
*/
setPreHook(hookFunction) {
if (typeof hookFunction !== "function" || hookFunction.length !== 1) {
throw new Error("preHook must be a function that accepts (config)");
}
this.preHook = hookFunction;
this.enableHooks();
}
/**
* Set a PostHook function that processes the response or error after execution.
* @param {(response: object | null, error: Error | null) => object | Promise<object> | void} hookFunction
*/
setPostHook(hookFunction) {
if (typeof hookFunction !== "function" || hookFunction.length !== 1) {
throw new Error("postHook must be a function that accepts (response)");
}
this.postHook = hookFunction;
this.enableHooks();
}
}
class HttpRequestOptions {
constructor(url, method, headers, params, data, timeout) {
this.setUrl(url);
this.setMethod(method);
if (headers) {
this.setHeaders(headers);
}
if (params) {
this.setParams(params);
}
if (data) {
this.setData(data);
}
if (timeout !== null && timeout !== undefined) this.setTimeout(timeout);
else this.timeout = 16000;
}
// Mandatory fields with validation
setUrl(url) {
if (!url) throw new Error("The 'url' property is required");
this.url = url;
}
setMethod(method) {
const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
if (!method || !validMethods.includes(method.toUpperCase())) {
throw new Error("The 'method' property is invalid or missing");
}
this.method = method.toUpperCase();
}
setData(data) {
if (data === undefined || data === null) {
throw new Error("The 'data' property is required");
}
this.data = data;
}
// Optional fields
setParams(params) {
if (params && typeof params !== 'object') {
throw new Error("The 'params' property must be an object");
}
this.params = params;
}
// Optional fields
setHeaders(headers) {
if (headers && typeof headers !== 'object') {
throw new Error("The 'headers' property must be an object");
}
this.headers = headers;
}
setTimeout(timeout) {
if (timeout === undefined || timeout === null || typeof timeout !== 'number') {
throw new Error("The 'timeout' property must be a number");
}
this.timeout = timeout;
}
}
// Default client is Axios
class DefaultHttpClient extends AbstractHttpClient{
constructor(timeout, httpsAgent) {
super();
if (timeout !== null && timeout !== undefined) this.setTimeout(timeout);
else this.timeout = 16000;
if (httpsAgent !== null && httpsAgent !== undefined) this.setHttpsAgent(httpsAgent);
else this.httpsAgent;
this._axiosInstance = axios__default["default"].create({});
}
enableHooks() {
if (this.preHook && typeof this.preHook === 'function') {
if (this.requestInterceptorId !== undefined) {
axios__default["default"].interceptors.request.eject(this.requestInterceptorId);
}
this.requestInterceptorId = this._axiosInstance.interceptors.request.use(
async (config) => {
config = await this.preHook(config); // Call the custom pre-hook
return config
},
(error) => {
// Handle errors before the request is sent
console.error('Request Pre-Hook Error:', error.message);
return Promise.reject(error);
}
);
}
if (this.postHook && typeof this.postHook === 'function') {
// Response interceptor (for post-hooks)
if (this.responseInterceptorId !== undefined) {
axios__default["default"].interceptors.response.eject(this.responseInterceptorId);
}
this.responseInterceptorId = this._axiosInstance.interceptors.response.use(
async (response) => {
response = await this.postHook(response); // Call the custom post-hook
return response
},
async (error) => {
console.error('Post-Hook: Response Error', error.message);
// Optionally call post-hook in case of errors
return Promise.reject(error);
}
);
}
}
request(httpRequestOptions) {
if(!(httpRequestOptions instanceof HttpRequestOptions)) {
throw new Error(`httpRequestOptions must be instance of HttpRequestOptions `);
}
const config = this.toAxiosConfig(httpRequestOptions);
return this._axiosInstance.request(config);
}
// Method to generate Axios-compatible config
toAxiosConfig(httpRequestOptions) {
if (!httpRequestOptions.url || !httpRequestOptions.method) {
throw new Error(
"Mandatory fields 'url' and 'method' must be set before making a request"
);
}
var config = {
url: httpRequestOptions.url,
method: httpRequestOptions.method
};
if (httpRequestOptions.params) config.params = httpRequestOptions.params;
if (httpRequestOptions.headers) config.headers = httpRequestOptions.headers;
if(httpRequestOptions.data) config.data = httpRequestOptions.data;
if (this.timeout != null && this.timeout != undefined) config.timeout = this.timeout;
if (this.httpsAgent) config.httpsAgent = this.httpsAgent;
return config;
}
}
const logLevels = {
levels: {
none: 0,
error: 1,
debug: 2,
trace: 3,
},
};
const logLevelEnum = {
level: {
LNone: 'none',
LError: 'error',
LDebug: 'debug',
LTrace: 'trace',
},
};
const logFormatEnum = {
formats: {
JSON: 'json',
TEXT: 'text',
},
};
class Logger {
get logLevelEnum() {
return logLevelEnum;
}
get logFormatEnum() {
return logFormatEnum;
}
constructor() {
this.log_level = logLevelEnum.level.LNone;
this.log_format = logFormatEnum.formats.TEXT;
this.log_to_console = true;
this.log_file_path;
this.log_response_body = false;
this.log_request_body = false;
this.setLogger();
}
setLogger() {
if(typeof window === 'undefined') {
const winston = require('winston');
this.logger = winston.createLogger({
levels: logLevels.levels,
level: this.log_level,
});
if (this.log_file_path && this.log_file_path !== '') {
if (this.log_format === logFormatEnum.formats.JSON) {
this.logger.add(new winston.transports.File({ format: winston.format.json(), filename: this.log_file_path }));
} else {
this.logger.add(
new winston.transports.File({
format: winston.format.combine(
winston.format((info) => {
info.level = info.level.toUpperCase();
return info;
})(),
winston.format.simple()
),
filename: this.log_file_path,
})
);
}
}
if (this.log_to_console) {
if (this.log_format === logFormatEnum.formats.JSON) {
this.logger.add(new winston.transports.Console({ format: winston.format.json() }));
} else {
this.logger.add(
new winston.transports.Console({
format: winston.format.combine(
winston.format((info) => {
info.level = info.level.toUpperCase();
return info;
})(),
winston.format.simple()
),
})
);
}
}
}
}
log(level, statusCode, method, url, requestHeaders, responseHeaders, requestBody, responseBody) {
var content = this.formatLog(level, statusCode, method, url, requestHeaders, responseHeaders, requestBody, responseBody);
if (typeof window !== 'undefined') {
var shouldLog = this.calculateLogLevel(level);
if (shouldLog > 0 && this.log_to_console === true) {
if(this.log_format === this.logFormatEnum.formats.JSON) {
console.log(content);
} else {
console.log(`${level.toUpperCase()}: ${content}`);
}
}
} else {
if (this.logger.transports.length > 0) this.logger.log(level, content);
}
}
calculateLogLevel(level) {
switch (this.log_level) {
case this.logLevelEnum.level.LError:
if (level !== this.logLevelEnum.level.LError) {
return -1;
}
return 1;
case this.logLevelEnum.level.LDebug:
if (level === this.logLevelEnum.level.LTrace) {
return -1;
}
return 1;
case this.logLevelEnum.level.LTrace:
return 1;
default:
return -1;
}
}
formatLog(level, statusCode, method, url, requestHeaders, responseHeaders, requestBody, responseBody) {
var result;
var localRequestHeaders = requestHeaders ? JSON.parse(JSON.stringify(requestHeaders)) : null;
var localResponseHeaders = responseHeaders ? JSON.parse(JSON.stringify(responseHeaders)) : null;
var localRequestBody = requestBody ? JSON.parse(JSON.stringify(requestBody)) : null;
var localResponseBody = responseBody ? JSON.parse(JSON.stringify(responseBody)) : null;
if (requestHeaders) localRequestHeaders['Authorization'] = '[REDACTED]';
if (!this.log_request_body) localRequestBody = undefined;
if (!this.log_response_body) localResponseBody = undefined;
if (this.log_format && this.log_format === logFormatEnum.formats.JSON) {
result = {
level: level,
date: new Date().toISOString(),
method: method,
url: decodeURIComponent(url),
correlationId: localResponseHeaders ? (localResponseHeaders['inin-correlation-id'] ? localResponseHeaders['inin-correlation-id'] : '') : '',
statusCode: statusCode,
};
if (localRequestHeaders) result.requestHeaders = localRequestHeaders;
if (localResponseHeaders) result.responseHeaders = localResponseHeaders;
if (localRequestBody) result.requestBody = localRequestBody;
if (localResponseBody) result.responseBody = localResponseBody;
} else {
result = `${new Date().toISOString()}
=== REQUEST ===
${this.formatValue('URL', decodeURIComponent(url))}${this.formatValue('Method', method)}${this.formatValue(
'Headers',
this.formatHeaderString(localRequestHeaders)
)}${this.formatValue('Body', localRequestBody ? JSON.stringify(localRequestBody, null, 2) : '')}
=== RESPONSE ===
${this.formatValue('Status', statusCode)}${this.formatValue('Headers', this.formatHeaderString(localResponseHeaders))}${this.formatValue(
'CorrelationId',
localResponseHeaders ? (localResponseHeaders['inin-correlation-id'] ? localResponseHeaders['inin-correlation-id'] : '') : ''
)}${this.formatValue('Body', localResponseBody ? JSON.stringify(localResponseBody, null, 2) : '')}`;
}
return result;
}
formatHeaderString(headers) {
var headerString = '';
if (!headers) return headerString;
for (const [key, value] of Object.entries(headers)) {
headerString += `\n\t${key}: ${value}`;
}
return headerString;
}
formatValue(key, value) {
if (!value || value === '' || value === '{}') return '';
return `${key}: ${value}\n`;
}
getLogLevel(level) {
switch (level) {
case 'error':
return logLevelEnum.level.LError;
case 'debug':
return logLevelEnum.level.LDebug;
case 'trace':
return logLevelEnum.level.LTrace;
default:
return logLevelEnum.level.LNone;
}
}
getLogFormat(format) {
switch (format) {
case 'json':
return logFormatEnum.formats.JSON;
default:
return logFormatEnum.formats.TEXT;
}
}
}
class Configuration {
/**
* Singleton getter
*/
get instance() {
return Configuration.instance;
}
/**
* Singleton setter
*/
set instance(value) {
Configuration.instance = value;
}
constructor() {
if (!Configuration.instance) {
Configuration.instance = this;
}
if (typeof window !== 'undefined') {
this.configPath = '';
} else {
const os = require('os');
const path = require('path');
this.configPath = path.join(os.homedir(), '.genesyscloudjavascript', 'config');
}
this.refresh_access_token = true;
this.refresh_token_wait_max = 10;
this.live_reload_config = true;
this.host;
this.environment;
this.basePath;
this.authUrl;
this.config;
this.gateway = undefined;
this.logger = new Logger();
this.setEnvironment();
this.liveLoadConfig();
}
liveLoadConfig() {
if (typeof window === 'undefined') {
// Please don't remove the typeof window === 'undefined' check here!
// This safeguards the browser environment from using `fs`, which is only
// available in node environment.
this.updateConfigFromFile();
if (this.live_reload_config && this.live_reload_config === true) {
try {
const fs = require('fs');
fs.watchFile(this.configPath, { persistent: false }, (eventType, filename) => {
this.updateConfigFromFile();
if (!this.live_reload_config) {
fs.unwatchFile(this.configPath);
}
});
} catch (err) {
// do nothing
}
}
return;
}
// If in browser, don't read config file, use default values
this.configPath = '';
}
setConfigPath(path) {
if (path && path !== this.configPath) {
this.configPath = path;
this.liveLoadConfig();
}
}
updateConfigFromFile() {
if (typeof window === 'undefined') {
// Please don't remove the typeof window === 'undefined' check here!
// This safeguards the browser environment from using `fs`, which is only
// available in node environment.
const ConfigParser = require('configparser');
try {
var configparser = new ConfigParser();
configparser.read(this.configPath); // If no error catched, indicates it's INI format
this.config = configparser;
} catch (error) {
if (error.name && error.name === 'MissingSectionHeaderError') {
// Not INI format, see if it's JSON format
const fs = require('fs');
var configData = fs.readFileSync(this.configPath, 'utf8');
this.config = {
_sections: JSON.parse(configData), // To match INI data format
};
}
}
if (this.config) this.updateConfigValues();
}
}
updateConfigValues() {
this.logger.log_level = this.logger.getLogLevel(this.getConfigString('logging', 'log_level'));
this.logger.log_format = this.logger.getLogFormat(this.getConfigString('logging', 'log_format'));
this.logger.log_to_console =
this.getConfigBoolean('logging', 'log_to_console') !== undefined
? this.getConfigBoolean('logging', 'log_to_console')
: this.logger.log_to_console;
this.logger.log_file_path =
this.getConfigString('logging', 'log_file_path') !== undefined
? this.getConfigString('logging', 'log_file_path')
: this.logger.log_file_path;
this.logger.log_response_body =
this.getConfigBoolean('logging', 'log_response_body') !== undefined
? this.getConfigBoolean('logging', 'log_response_body')
: this.logger.log_response_body;
this.logger.log_request_body =
this.getConfigBoolean('logging', 'log_request_body') !== undefined
? this.getConfigBoolean('logging', 'log_request_body')
: this.logger.log_request_body;
this.refresh_access_token =
this.getConfigBoolean('reauthentication', 'refresh_access_token') !== undefined
? this.getConfigBoolean('reauthentication', 'refresh_access_token')
: this.refresh_access_token;
this.refresh_token_wait_max =
this.getConfigInt('reauthentication', 'refresh_token_wait_max') !== undefined
? this.getConfigInt('reauthentication', 'refresh_token_wait_max')
: this.refresh_token_wait_max;
this.live_reload_config =
this.getConfigBoolean('general', 'live_reload_config') !== undefined
? this.getConfigBoolean('general', 'live_reload_config')
: this.live_reload_config;
this.host = this.getConfigString('general', 'host') !== undefined ? this.getConfigString('general', 'host') : this.host;
if (this.getConfigString('gateway', 'host') !== undefined) {
let gateway = {
host: this.getConfigString('gateway', 'host')
};
if (this.getConfigString('gateway', 'protocol') !== undefined) gateway.protocol = this.getConfigString('gateway', 'protocol');
if (this.getConfigInt('gateway', 'port') !== undefined) gateway.port = this.getConfigInt('gateway', 'port');
if (this.getConfigString('gateway', 'path_params_login') !== undefined) gateway.path_params_login = this.getConfigString('gateway', 'path_params_login');
if (this.getConfigString('gateway', 'path_params_api') !== undefined) gateway.path_params_api = this.getConfigString('gateway', 'path_params_api');
if (this.getConfigString('gateway', 'username') !== undefined) gateway.username = this.getConfigString('gateway', 'username');
if (this.getConfigString('gateway', 'password') !== undefined) gateway.password = this.getConfigString('gateway', 'password');
this.setGateway(gateway);
} else {
this.setGateway();
}
this.setEnvironment();
// Update logging configs
this.logger.setLogger();
}
/**
* @description Sets the gateway used by the session
* @param {object} gateway - Gateway Configuration interface
* @param {string} gateway.host - The address of the gateway.
* @param {string} gateway.protocol - (optional) The protocol to use. It will default to "https" if the parameter is not defined or empty.
* @param {number} gateway.port - (optional) The port to target. This parameter can be defined if a non default port is used and needs to be specified in the url (value must be greater or equal to 0).
* @param {string} gateway.path_params_login - (optional) An arbitrary string to be appended to the gateway url path for Login requests.
* @param {string} gateway.path_params_api - (optional) An arbitrary string to be appended to the gateway url path for API requests.
* @param {string} gateway.username - (optional) Not used at this stage (for a possible future use).
* @param {string} gateway.password - (optional) Not used at this stage (for a possible future use).
*/
setGateway(gateway) {
if (gateway) {
this.gateway = {
host: ''
};
if (gateway.protocol) this.gateway.protocol = gateway.protocol;
else this.gateway.protocol = 'https';
if (gateway.host) this.gateway.host = gateway.host;
else this.gateway.host = '';
if (gateway.port && gateway.port > -1) this.gateway.port = gateway.port;
else this.gateway.port = -1;
if (gateway.path_params_login) {
this.gateway.path_params_login = gateway.path_params_login;
// Strip trailing slash
this.gateway.path_params_login = this.gateway.path_params_login.replace(/\/+$/, '');
} else this.gateway.path_params_login = '';
if (gateway.path_params_api) {
this.gateway.path_params_api = gateway.path_params_api;
// Strip trailing slash
this.gateway.path_params_api = this.gateway.path_params_api.replace(/\/+$/, '');
} else this.gateway.path_params_api = '';
if (gateway.username) this.gateway.username = gateway.username;
if (gateway.password) this.gateway.password = gateway.password;
} else {
this.gateway = undefined;
}
}
setEnvironment(env) {
// Default value
if (env) this.environment = env;
else this.environment = this.host ? this.host : 'mypurecloud.com';
// Strip trailing slash
this.environment = this.environment.replace(/\/+$/, '');
// Strip protocol and subdomain
if (this.environment.startsWith('https://')) this.environment = this.environment.substring(8);
if (this.environment.startsWith('http://')) this.environment = this.environment.substring(7);
if (this.environment.startsWith('api.')) this.environment = this.environment.substring(4);
this.basePath = `https://api.${this.environment}`;
this.authUrl = `https://login.${this.environment}`;
}
getConfUrl(pathType, regionUrl) {
if (!this.gateway) return regionUrl;
if (!this.gateway.host) return regionUrl;
var url = this.gateway.protocol + '://' + this.gateway.host;
if (this.gateway.port > -1) url = url + ':' + this.gateway.port.toString();
if (pathType === 'login') {
if (this.gateway.path_params_login) {
if (this.gateway.path_params_login.startsWith('/')) url = url + this.gateway.path_params_login;
else url = url + '/' + this.gateway.path_params_login;
}
} else {
if (this.gateway.path_params_api) {
if (this.gateway.path_params_api.startsWith('/')) url = url + this.gateway.path_params_api;
else url = url + '/' + this.gateway.path_params_api;
}
}
return url;
}
getConfigString(section, key) {
if (this.config._sections[section]) return this.config._sections[section][key];
}
getConfigBoolean(section, key) {
if (this.config._sections[section] && this.config._sections[section][key] !== undefined) {
if (typeof this.config._sections[section][key] === 'string') {
return this.config._sections[section][key] === 'true';
} else return this.config._sections[section][key];
}
}
getConfigInt(section, key) {
if (this.config._sections[section] && this.config._sections[section][key]) {
if (typeof this.config._sections[section][key] === 'string') {
return parseInt(this.config._sections[section][key]);
} else return this.config._sections[section][key];
}
}
}
/**
* @module purecloud-platform-client-v2/ApiClient
* @version 223.0.0
*/
class ApiClient {
/**
* Singleton getter
*/
get instance() {
return ApiClient.instance;
}
/**
* Singleton setter
*/
set instance(value) {
ApiClient.instance = value;
}
/**
* Manages low level client-server communications, parameter marshalling, etc. There should not be any need for an
* application to use this class directly - the *Api and model classes provide the public API for the service. The
* contents of this file should be regarded as internal but are documented for completeness.
* @alias module:purecloud-platform-client-v2/ApiClient
* @class
*/
constructor() {
/**
* @description The default API client implementation.
* @type {module:purecloud-platform-client-v2/ApiClient}
*/
if(!ApiClient.instance){
ApiClient.instance = this;
}
/**
* Enumeration of collection format separator strategies.
* @enum {String}
* @readonly
*/
this.CollectionFormatEnum = {
/**
* Comma-separated values. Value: <code>csv</code>
* @const
*/
CSV: ',',
/**
* Space-separated values. Value: <code>ssv</code>
* @const
*/
SSV: ' ',
/**
* Tab-separated values. Value: <code>tsv</code>
* @const
*/
TSV: '\t',
/**
* Pipe(|)-separated values. Value: <code>pipes</code>
* @const
*/
PIPES: '|',
/**
* Native array. Value: <code>multi</code>
* @const
*/
MULTI: 'multi'
};
/**
* @description Value is `true` if local storage exists. Otherwise, false.
*/
try {
localStorage.setItem('purecloud_local_storage_test', 'purecloud_local_storage_test');
localStorage.removeItem('purecloud_local_storage_test');
this.hasLocalStorage = true;
} catch(e) {
this.hasLocalStorage = false;
}
/**
* The authentication methods to be included for all API calls.
* @type {Array.<String>}
*/
this.authentications = {
'Guest Chat JWT': {type: 'apiKey', 'in': 'header', name: 'Authorization'},
'PureCloud OAuth': {type: 'oauth2'}
};
/**
* The default HTTP headers to be included for all API calls.
* @type {Array.<String>}
* @default {}
*/
this.defaultHeaders = {};
/**
* The default HTTP timeout for all API calls.
* @type {Number}
* @default 16000
*/
this.timeout = 16000;
this.authData = {};
this.settingsPrefix = 'purecloud';
// Transparently request a new access token when it expires (Code Authorization only)
this.refreshInProgress = false;
this.httpClient;
this.proxyAgent;
this.config = new Configuration();
if (typeof(window) !== 'undefined') window.ApiClient = this;
}
/**
* @description If set to `true`, the response object will contain additional information about the HTTP response. When `false` (default) only the body object will be returned.
* @param {boolean} returnExtended - `true` to return extended responses
*/
setReturnExtendedResponses(returnExtended) {
this.returnExtended = returnExtended;
}
/**
* @description When `true`, persists the auth token to local storage to avoid a redirect to the login page on each page load. Defaults to `false`.
* @param {boolean} doPersist - `true` to persist the auth token to local storage
* @param {string} prefix - (Optional, default 'purecloud') The name prefix used for the local storage key
*/
setPersistSettings(doPersist, prefix) {
this.persistSettings = doPersist;
this.settingsPrefix = prefix ? prefix.replace(/\W+/g, '_') : 'purecloud';
}
/**
* @description Saves the auth token to local storage, if enabled.
*/
_saveSettings(opts) {
try {
this.authData.accessToken = opts.accessToken;
this.authentications['PureCloud OAuth'].accessToken = opts.accessToken;
if (opts.state) {
this.authData.state = opts.state;
}
this.authData.error = opts.error;
this.authData.error_description = opts.error_description;
if (opts.tokenExpiryTime) {
this.authData.tokenExpiryTime = opts.tokenExpiryTime;
this.authData.tokenExpiryTimeString = opts.tokenExpiryTimeString;
}
// Don't save settings if we aren't supposed to be persisting them
if (this.persistSettings !== true) return;
// Ensure we can access local storage
if (!this.hasLocalStorage) {
return;
}
// Remove state from data so it's not persisted
let tempData = JSON.parse(JSON.stringify(this.authData));
delete tempData.state;
// Save updated auth data
localStorage.setItem(`${this.settingsPrefix}_auth_data`, JSON.stringify(tempData));
} catch (e) {
console.error(e);
}
}
/**
* @description Loads settings from local storage, if enabled.
*/
_loadSettings() {
// Don't load settings if we aren't supposed to be persisting them
if (this.persistSettings !== true) return;
// Ensure we can access local storage
if (!this.hasLocalStorage) {
return;
}
// Load current auth data
const tempState = this.authData.state;
this.authData = localStorage.getItem(`${this.settingsPrefix}_auth_data`);
if (!this.authData)
this.authData = {};
else
this.authData = JSON.parse(this.authData);
if (this.authData.accessToken) this.setAccessToken(this.authData.accessToken);
this.authData.state = tempState;
}
/**
* @description Sets the environment used by the session
* @param {string} environment - (Optional, default "mypurecloud.com") Environment the session use, e.g. mypurecloud.ie, mypurecloud.com.au, etc.
*/
setEnvironment(environment) {
this.config.setEnvironment(environment);
}
/**
* @description Sets the dynamic HttpClient used by the client
* @param {object} httpClient - HttpClient to be injected
*/
setHttpClient(httpClient) {
if (!(httpClient instanceof AbstractHttpClient)) {
throw new Error("httpclient must be an instance of AbstractHttpClient. See DefaultltHttpClient for a prototype");
}
this.httpClient = httpClient;
}
/**
* @description Gets the HttpClient used by the client
*/
getHttpClient() {
if (this.httpClient) {
return this.httpClient;
} else {
this.httpClient = new DefaultHttpClient(this.timeout, this.proxyAgent);
return this.httpClient;
}
}
/**
* @description Sets the certificate paths if MTLS authentication is needed
* @param {string} certPath - path for certs
* @param {string} keyPath - path for key
* @param {string} caPath - path for public certs
*/
setMTLSCertificates(certPath, keyPath, caPath) {
if (typeof window === 'undefined') {
const agentOptions = {};
if (certPath) {
agentOptions.cert = require('fs').readFileSync(certPath);
}
if (keyPath) {
agentOptions.key = require('fs').readFileSync(keyPath);
}
if (caPath) {
agentOptions.ca = require('fs').readFileSync(caPath);
}
agentOptions.rejectUnauthorized = true;
this.proxyAgent = new require('https').Agent(agentOptions);
const httpClient = this.getHttpClient();
httpClient.setHttpsAgent(this.proxyAgent);
} else {
throw new Error("MTLS authentication is managed by the Browser itself. MTLS certificates cannot be set via code on Browser.");
}
}
/**
* @description Sets preHook functions for the httpClient
* @param {string} preHook - method definition for prehook
*/
setPreHook(preHook) {
const httpClient = this.getHttpClient();
httpClient.setPreHook(preHook);
}
/**
* @description Sets postHook functions for the httpClient
* @param {string} postHook - method definition for posthook
*/
setPostHook(postHook) {
const httpClient = this.getHttpClient();
httpClient.setPostHook(postHook);
}
/**
* @description Sets the certificate content if MTLS authentication is needed
* @param {string} certContent - content for certs
* @param {string} keyContent - content for key
* @param {string} caContent - content for public certs
*/
setMTLSContents(certContent, keyContent, caContent) {
if (typeof window === 'undefined') {
const agentOptions = {};
if (certContent) {
agentOptions.cert = certContent;
}
if (keyContent) {
agentOptions.key = keyContent;
}
if (caContent) {
agentOptions.ca = caContent;
}
agentOptions.rejectUnauthorized = true;
this.proxyAgent = new require('https').Agent(agentOptions);
const httpClient = this.getHttpClient();
httpClient.setHttpsAgent(this.proxyAgent);
} else {
throw new Error("MTLS authentication is managed by the Browser itself. MTLS certificates cannot be set via code on Browser.");
}
}
/**
* @description Sets the gateway used by the session
* @param {object} gateway - Gateway Configuration interface
* @param {string} gateway.host - The address of the gateway.
* @param {string} gateway.protocol - (optional) The protocol to use. It will default to "https" if the parameter is not defined or empty.
* @param {string} gateway.port - (optional) The port to target. This parameter can be defined if a non default port is used and needs to be specified in the url (value must be greater than 0).
* @param {string} gateway.path_params_login - (optional) An arbitrary string to be appended to the gateway url path for Login requests.
* @param {string} gateway.path_params_api - (optional) An arbitrary string to be appended to the gateway url path for API requests.
* @param {string} gateway.username - (optional) Not used at this stage (for a possible future use).
* @param {string} gateway.password - (optional) Not used at this stage (for a possible future use).
*/
setGateway(gateway) {
this.config.setGateway(gateway);
}
/**
* @description Initiates the implicit grant login flow. Will attempt to load the token from local storage, if enabled.
* @param {string} clientId - The client ID of an OAuth Implicit Grant client
* @param {string} redirectUri - The redirect URI of the OAuth Implicit Grant client
* @param {object} opts - (optional) Additional options
* @param {string} opts.state - (optional) An arbitrary string to be passed back with the login response. Used for client apps to associate login responses with a request.
* @param {string} opts.org - (optional) The organization name that would normally used when specifying an organization name when logging in. This is only used when a provider is also specified.
* @param {string} opts.provider - (optional) Authentication provider to log in with e.g. okta, adfs, salesforce, onelogin. This is only used when an org is also specified.
*/
loginImplicitGrant(clientId, redirectUri, opts) {
// Check for auth token in hash
const hash = this._setValuesFromUrlHash();
this.clientId = clientId;
this.redirectUri = redirectUri;
if (!opts) opts = {};
return new Promise((resolve, reject) => {
// Abort if org and provider are not set together
if (opts.org && !opts.provider) {
reject(new Error('opts.provider must be set if opts.org is set'));
} else if (opts.provider && !opts.org) {
reject(new Error('opts.org must be set if opts.provider is set'));
}
// Abort on auth error
if (hash && hash.error) {
hash.accessToken = undefined;
this._saveSettings(hash);
return reject(new Error(`[${hash.error}] ${hash.error_description}`));
}
// Test token and proceed with login
this._testTokenAccess()
.then(() => {
if (!this.authData.state && opts.state)
this.authData.state = opts.state;
resolve(this.authData);
})
.catch((error) => {
var query = {
client_id: encodeURIComponent(this.clientId),
redirect_uri: encodeURIComponent(this.redirectUri),
response_type: 'token'
};
if (opts.state) query.state = encodeURIComponent(opts.state);
if (opts.org) query.org = encodeURIComponent(opts.org);
if (opts.provider) query.provider = encodeURIComponent(opts.provider);
var url = this._buildAuthUrl('oauth/authorize', query);
window.location.replace(url);
});
});
}
/**
* @description Initiates the client credentials login flow. Only available in node apps.
* @param {string} clientId - The client ID of an OAuth Implicit Grant client
* @param {string} clientSecret - The client secret of an OAuth Implicit Grant client
*/
loginClientCredentialsGrant(clientId, clientSecret) {
this.clientId = clientId;
var authHeader = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
var loginBasePath = this.config.getConfUrl('login', `https://login.${this.config.environment}`);
return new Promise((resolve, reject) => {
// Block browsers from using client credentials
if (typeof window !== 'undefined') {
reject(new Error('The client credentials grant is not supported in a browser.'));
return;
}
const headers = {
'Authorization': `Basic ${authHeader}`
};
var requestOptions = new HttpRequestOptions(`${loginBasePath}/oauth/token`, `POST`, headers, null, 'grant_type=client_credentials', this.timeout);
const httpClient = this.getHttpClient();
httpClient.request(requestOptions)
.then((response) => {
// Logging
this.config.logger.log(
'trace',
response.status,
'POST',
`${loginBasePath}/oauth/token`,
headers,
response.headers,
{ grant_type: 'client_credentials' },
undefined
);
this.config.logger.log(
'debug',
response.status,
'POST',
`${loginBasePath}/oauth/token`,
headers,
undefined,
{ grant_type: 'client_credentials' },
undefined
);
// Save access token
this.setAccessToken(response.data['access_token']);
// Set expiry time
this.authData.tokenExpiryTime = (new Date()).getTime() + (response.data['expires_in'] * 1000);
this.authData.tokenExpiryTimeString = (new Date(this.authData.tokenExpiryTime)).toUTCString();
// Return auth data
resolve(this.authData);
})
.catch((error) => {
// Log error
if (error.response) {
this.config.logger.log(
'error',
error.response.status,
'POST',
`${loginBasePath}/oauth/token`,
headers,
error.response.headers,
{ grant_type: 'client_credentials' },
error.response.data
);
}
reject(error);
});
});
}
/**
* @description Initiates the Saml2Bearerflow. Only available in node apps.
* @param {string} clientId - The client ID of an OAuth Implicit Grant client
* @param {string} clientSecret - The client secret of an OAuth Implicit Grant client
* @param {string} orgName - The orgName of an OAuth Implicit Grant client
* @param {string} assertion - The saml2bearer assertion
*/
loginSaml2BearerGrant(clientId, clientSecret, orgName, assertion) {
this.clientId = clientId;
var loginBasePath = this.config.getConfUrl('login', `https://login.${this.config.environment}`);
return new Promise((resolve, reject) => {
if (typeof window !== 'undefined') {
reject(new Error('The saml2bearer grant is not supported in a browser.'));
return;
}
var encodedData = Buffer.from(clientId + ':' + clientSecret).toString('base64');
var request = this._formAuthRequest(encodedData,
{ grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
orgName: orgName,
assertion: assertion });
request.proxy = this.proxy;
var bodyParam = {
grant_type: 'urn:ietf:params:oauth:grant-type:saml2-bearer',
orgName: orgName,
assertion: assertion,
};
// Handle response
request
.then((response) => {
// Logging
this.config.logger.log(
'trace',
response.status,
'POST',
`${loginBasePath}/oauth/token`,
request.headers,
response.headers,
bodyParam,
undefined
);
this.config.logger.log(
'debug',
response.status,
'POST',
`${loginBasePath}/oauth/token`,
request.headers,
undefined,
bodyParam,
undefined
);
// Get access token from response
var access_token = response.data.access_token;
this.setAccessToken(access_token);
this.authData.tokenExpiryTime = new Date().getTime() + response.data['expires_in'] * 1000;
this.authData.tokenExpiryTimeString = new Date(this.authData.tokenExpiryTime).toUTCString();
// Return auth data
resolve(this.authData);
})
.catch((error) => {
// Log error
if (error.response) {
this.config.logger.log(
'error',
error.response.status,
'POST',
`${loginBasePath}/oauth/token`,
request.headers,
error.response.headers,
bodyParam,
error.response.data
);
}
reject(error);
});
});
}
/**
* @description Completes the PKCE Code Authorization.
* @param {string} clientId - The client ID of an OAuth Code Authorization Grant client
* @param {string} codeVerifier - code verifier used to generate the code challenge
* @param {string} authCode - Authorization code
* @param {string} redirectUri - Authorized redirect URI for your Code Authorization client
*/
authorizePKCEGrant(clientId, codeVerifier, authCode, redirectUri) {
this.clientId = clientId;
var loginBasePath = this.config.getConfUrl('login', `https://login.${this.config.environment}`);
return new Promise((resolve, reject) => {
var headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
var data = qs__default["default"].stringify({ grant_type: 'authorization_code',
code: authCode,
code_verifier: codeVerifier,
client_id: clientId,
redirect_uri: redirectUri });
var requestOptions = new HttpRequestOptions(`${loginBasePath}/oauth/token`, `POST`, headers, null, data, this.timeout);
const httpClient = this.getHttpClient();
var bodyParam = {
grant_type: 'authorization_code',
code: authCode,
code_verifier: codeVerifier,
client_id: clientId,
redirect_uri: redirectUri,
};
// Handle response
httpClient.request(requestOptions)
.then((response) => {
// Logging
this.config.logger.log(
'trace',
response.status,
'POST',
`${loginBasePath}/oauth/token`,
requestOptions.headers,
response.headers,
bodyParam,
undefined
);
this.config.logger.log(
'debug',
response.status,
'POST',
`${loginBasePath}/oauth/token`,
requestOptions.headers,
undefined,
bodyParam,
undefined
);
// Get access token from response
var access_token = response.data.access_token;
this.setAccessToken(access_token);
this.authData.tokenExpiryTime = new Date().getTime() + response.data['expires_in'] * 1000;
this.authData.tokenExpiryTimeString = new Date(this.authData.tokenExpiryTime).toUTCString();
// Return auth data
resolve(this.authData);
})
.catch((error) => {
// Log error
if (error.response) {
this.config.logger.log(
'error',
error.response.status,
'POST',
`${loginBasePath}/oauth/token`,
requestOptions.headers,
error.response.headers,
bodyParam,
error.response.data
);
}
reject(error);
});
});
}
/**
* @description Generate a random string used as PKCE Code Verifier - length = 43 to 128.
* @param {number} nChar - code length
*/
generatePKCECodeVerifier(nChar) {
if (nChar < 43 || nChar > 128) {
throw new Error(`PKCE Code Verifier (length) must be between 43 and 128 characters`);
}
// Check for window
if (typeof window === 'undefined') {
try {
const getRandomValues = require('crypto').getRandomValues;
const unreservedCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~";
let randomString = Array.from(getRandomValues(new Uint32Array(nChar)))
.map((x) => unreservedCharacters[x % unreservedCharacters.length])
.join('');
return randomString;
} catch (err) {
throw new Error(`Crypto module is missing/not supported.`);
}
} else {
const unreservedCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~";
let randomString = Array.from(crypto.getRandomValues(new Uint32Array(nChar)))
.map((x) => unreservedCharacters[x % unreservedCharacters.length])
.join('');
return randomString;
}
}
/**
* @description Compute Base64Url PKCE Code Challenge from Code Verifier.
* @param {string} code - code verifier used to generate the code challenge
*/
computePKCECodeChallenge(code) {
if (code.length < 43 || code.length > 128) {
throw new Error(`PKCE Code Verifier (length) must be between 43 and 128 characters`);
}
// Check for window
if (typeof window === 'undefined') {
// nodejs
try {
const createHash = require('crypto').createHash;
const utf8 = new TextEncoder().encode(code);
return new Promise((resolve, reject) => {
const hashHex = createHash('sha256').update(utf8).digest();
const hashBase64Url = Buffer.from(hashHex).toString('base64url');
resolve(hashBase64Url);
});
} catch (err) {
throw new Error(`Crypto module is missing/not supported.`);
}
} else {
// browser
const utf8 = new TextEncoder().encode(code);
return new Promise((resolve, reject) => {
window.crypto.subtle.digest("SHA-256", utf8).then((hashBuffer) => {
const hashBase64 = Buffer.from(hashBuffer).toString('base64');
let hashBase64Url = hashBase64.replaceAll("+", "-").replaceAll("/", "_");
hashBase64Url = hashBase64Url.split("=")[0];
resolve(hashBase64Url);
})
.catch((error) => {
// Handle failure
return reject(new Error(`Code Challenge Error ${error}`));
});
});
}
}
/**
* @description Initiates the pkce auth code grant login flow. Will attempt to load the token from local storage, if enabled.
* @param {string} clientId - The client ID of an OAuth Implicit Grant client
* @param {string} redirectUri - The redirect URI of the OAuth Implicit Grant client
* @param {object} opts - (optional) Additional options
* @param {string} opts.state - (optional) An arbitrary string to be passed back with the login response. Used for client apps to associate login responses with a request.
* @param {string} opts.org - (optional) The organization name that would normally used when specifying an organization name when logging in. This is only used when a provider is also specified.
* @param {string} opts.provider - (optional) Authentication provider to log in with e.g. okta, adfs, salesforce, onelogin. This is only used when an org is also specified.
* @param {string} codeVerifier - (optional) code verifier used to generate the code challenge
*/
loginPKCEGrant(clientId, redirectUri, opts, codeVerifier) {
// Need Local Storage or non null codeVerifier as parameter
if (!this.hasLocalStorage && !codeVerifier) {
throw new Error(`loginPKCEGrant requires Local Storage or codeVerifier as input parameter`);
}
// Check for auth code in query
const query = this._setValuesFromUrlQuery();
this.clientId = clientId;
this.redirectUri = redirectUri;
this.codeVerifier = codeVerifier;
if (!opts) opts = {};
return new Promise((resolve, reject) => {
// Abort if org and provider are not set together
if (opts.org && !opts.provider) {
reject(new Error('opts.provider must be set if opts.org is set'));
} else if (opts.provider && !opts.org) {
reject(new Error('opts.org must be set if opts.provider is set'));
}
// Abort on auth error
if (query && query.error) {
// remove codeVerifier from session storage
if (this.hasLocalStorage) {
sessionStorage.removeItem(`genesys_cloud_sdk_pkce_code_verifier`);
}
// reset access token if any was stored
this._saveSettings({ accessToken: undefined });
return reject(new Error(`[${query.error}] ${query.error_description}`));
}
// Get token on auth code
if (query && query.code) {
if (!this.codeVerifier) {
// load codeVerifier from session storage
if (this.hasLocalStorage) {
this.codeVerifier = sessionStorage.getItem(`genesys_cloud_sdk_pkce_code_verifier`);
}
}
this.authorizePKCEGrant(this.clientId, this.codeVerifier, query.code, this.redirectUri)
.then(() => {
// Do authenticated things
this._testTokenAccess()
.then(() => {
if (!this.authData.state && query.state)
this.authData.state = query.state;
// remove codeVerifier from session storage
if (this.hasLocalStorage) {
sessionStorage.removeItem(`genesys_cloud_sdk_pkce_code_verifier`);
}
resolve(this.authData);
})
.catch((error) => {
// Handle failure response
this._saveSettings({ accessToken: undefined});
// remove codeVerifier from session storage
if (this.hasLocalStorage) {
sessionStorage.removeItem(`genesys_cloud_sdk_pkce_code_verifier`);
}
return reject(new Error(`[${error.name}] ${error.msg}`));
});
})
.catch((error) => {
// Handle failure response
this._saveSettings({ accessToken: undefined});
// remove codeVerifier from session storage
if (this.hasLocalStorage) {
sessionStorage.removeItem(`genesys_cloud_sdk_pkce_code_verifier`);
}
return reject(new Error(`[${error.name}] ${error.msg}`));
});
} else {
// Test token (if previously stored) and proceed with login
this._testTokenAccess()
.then(() => {
if (!this.authData.state && opts.state)
this.authData.state = opts.state;
resolve(this.authData);
})
.catch((error) => {
if (!this.codeVerifier) {
this.codeVerifier = this.generatePKCECodeVerifier(128);
// save codeVerifier in session storage
if (this.hasLocalStorage) {
sessionStorage.setItem(`genesys_cloud_sdk_pkce_code_verifier`, this.codeVerifier);
}
}
this.computePKCECodeChalle