amazon-connect-streams
Version:
Amazon Connect Streams Library
1,493 lines (1,327 loc) • 1.03 MB
JavaScript
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ 94
() {
/*
* Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
(function () {
var global = this || globalThis;
var connect = global.connect || {};
global.connect = connect;
global.lily = connect;
var userAgent = navigator.userAgent;
var ONE_DAY_MILLIS = 24 * 60 * 60 * 1000;
var DEFAULT_POPUP_HEIGHT = 578;
var DEFAULT_POPUP_WIDTH = 433;
var COPYABLE_EVENT_FIELDS = ["bubbles", "cancelBubble", "cancelable", "composed", "data", "defaultPrevented", "eventPhase", "isTrusted", "lastEventId", "origin", "returnValue", "timeStamp", "type"];
/**
* Unpollute sprintf functions from the global namespace.
*/
connect.sprintf = global.sprintf;
connect.vsprintf = global.vsprintf;
delete global.sprintf;
delete global.vsprintf;
connect.HTTP_STATUS_CODES = {
SUCCESS: 200,
UNAUTHORIZED: 401,
ACCESS_DENIED: 403,
TOO_MANY_REQUESTS: 429,
INTERNAL_SERVER_ERROR: 500
};
connect.TRANSPORT_TYPES = {
CHAT_TOKEN: "chat_token",
WEB_SOCKET: "web_socket",
AGENT_DISCOVERY: "agent_discovery",
WEB_RTC: "web_rtc"
};
/**
* Binds the given instance object as the context for
* the method provided.
*
* @param scope The instance object to be set as the scope
* of the function.
* @param method The method to be encapsulated.
*
* All other arguments, if any, are bound to the method
* invocation inside the closure.
*
* @return A closure encapsulating the invocation of the
* method provided in context of the given instance.
*/
connect.hitch = function () {
var args = Array.prototype.slice.call(arguments);
var scope = args.shift();
var method = args.shift();
connect.assertNotNull(scope, 'scope');
connect.assertNotNull(method, 'method');
connect.assertTrue(connect.isFunction(method), 'method must be a function');
return function () {
var closureArgs = Array.prototype.slice.call(arguments);
return method.apply(scope, args.concat(closureArgs));
};
};
/**
* Determine if the given value is a callable function type.
* Borrowed from Underscore.js.
*/
connect.isFunction = function (obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
};
/**
* Determine if the given value is an array.
*/
connect.isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
/**
* Get a list of keys from a Javascript object used
* as a hash map.
*/
connect.keys = function (map) {
var keys = [];
connect.assertNotNull(map, 'map');
for (var k in map) {
keys.push(k);
}
return keys;
};
/**
* Get a list of values from a Javascript object used
* as a hash map.
*/
connect.values = function (map) {
var values = [];
connect.assertNotNull(map, 'map');
for (var k in map) {
values.push(map[k]);
}
return values;
};
/**
* Get a list of key/value pairs from the given map.
*/
connect.entries = function (map) {
var entries = [];
for (var k in map) {
entries.push({ key: k, value: map[k] });
}
return entries;
};
/**
* Merge two or more maps together into a new map,
* or simply copy a single map.
*/
connect.merge = function () {
var argMaps = Array.prototype.slice.call(arguments, 0);
var resultMap = {};
argMaps.forEach(function (map) {
connect.entries(map).forEach(function (kv) {
resultMap[kv.key] = kv.value;
});
});
return resultMap;
};
connect.now = function () {
return new Date().getTime();
};
connect.find = function (array, predicate) {
for (var x = 0; x < array.length; x++) {
if (predicate(array[x])) {
return array[x];
}
}
return null;
};
connect.contains = function (obj, value) {
if (obj instanceof Array) {
return connect.find(obj, function (v) { return v === value; }) != null;
} else {
return (value in obj);
}
};
connect.containsValue = function (obj, value) {
if (obj instanceof Array) {
return connect.find(obj, function (v) { return v === value; }) != null;
} else {
return connect.find(connect.values(obj), function (v) { return v === value; }) != null;
}
};
/**
* Generate a random ID consisting of the current timestamp
* and a random base-36 number based on Math.random().
*/
connect.randomId = function () {
return connect.sprintf("%s-%s", connect.now(), Math.random().toString(36).slice(2));
};
/**
* Generate an enum from the given list of lower-case enum values,
* where the enum keys will be upper case.
*
* Conversion from pascal case based on code from here:
* http://stackoverflow.com/questions/30521224
*/
connect.makeEnum = function (values) {
var enumObj = {};
values.forEach(function (value) {
var key = value.replace(/\.?([a-z]+)_?/g, function (x, y) { return y.toUpperCase() + "_"; })
.replace(/_$/, "");
enumObj[key] = value;
});
return enumObj;
};
connect.makeNamespacedEnum = function (prefix, values) {
var enumObj = connect.makeEnum(values);
connect.keys(enumObj).forEach(function (key) {
enumObj[key] = connect.sprintf("%s::%s", prefix, enumObj[key]);
});
return enumObj;
};
connect.makeGenericNamespacedEnum = function (prefix, values, delimiter) {
var enumObj = connect.makeEnum(values);
connect.keys(enumObj).forEach(function (key) {
enumObj[key] = connect.sprintf("%s"+delimiter+"%s", prefix, enumObj[key]);
});
return enumObj;
};
/**
* Methods to determine browser type and versions, used for softphone initialization.
*/
/*
This will also return True for Edge and Opera since they
include Chrome in the user agent string, as they are built
off of Chrome.
*/
connect.isChromeBrowser = function () {
return userAgent.indexOf("Chrome") !== -1;
};
connect.isFirefoxBrowser = function () {
return userAgent.indexOf("Firefox") !== -1;
};
connect.isOperaBrowser = function () {
return userAgent.indexOf("Opera") !== -1;
};
connect.isEdgeBrowser = function () {
return userAgent.indexOf("Edg") !== -1;
};
connect.getChromeBrowserVersion = function () {
var chromeVersion = userAgent.substring(userAgent.indexOf("Chrome") + 7);
if (chromeVersion) {
return parseFloat(chromeVersion);
} else {
return -1;
}
};
connect.getFirefoxBrowserVersion = function () {
var firefoxVersion = userAgent.substring(userAgent.indexOf("Firefox") + 8);
if (firefoxVersion) {
return parseFloat(firefoxVersion);
} else {
return -1;
}
};
connect.isValidLocale = function (locale) {
var languages = [
{
id: 'en_US',
label: 'English'
},
{
id: 'de_DE',
label: 'Deutsch'
},
{
id: 'es_ES',
label: 'Español'
},
{
id: 'fr_FR',
label: 'Français'
},
{
id: 'ja_JP',
label: '日本語'
},
{
id: 'it_IT',
label: 'Italiano'
},
{
id: 'ko_KR',
label: '한국어'
},
{
id: 'pt_BR',
label: 'Português'
},
{
id: 'zh_CN',
label: '中文(简体)'
},
{
id: 'zh_TW',
label: '中文(繁體)'
}
];
return languages.map(function(language){ return language.id}).includes(locale);
}
connect.getOperaBrowserVersion = function () {
var versionOffset = userAgent.indexOf("Opera");
var operaVersion = (userAgent.indexOf("Version") !== -1) ? userAgent.substring(versionOffset + 8) : userAgent.substring(versionOffset + 6);
if (operaVersion) {
return parseFloat(operaVersion);
} else {
return -1;
}
};
/**
* Return a map of items in the given list indexed by
* keys determined by the closure provided.
*
* @param iterable A list-like object.
* @param closure A closure to determine the index for the
* items in the iterable.
* @return A map from index to item for each item in the iterable.
*/
connect.index = function (iterable, closure) {
var map = {};
iterable.forEach(function (item) {
map[closure(item)] = item;
});
return map;
};
/**
* Converts the given array into a map as a set,
* where elements in the array are mapped to 1.
*/
connect.set = function (arrayIn) {
var setMap = {};
arrayIn.forEach(function (key) {
setMap[key] = 1;
});
return setMap;
};
/**
* Returns a map for each key in mapB which
* is NOT in mapA.
*/
connect.relativeComplement = function (mapA, mapB) {
var compMap = {};
connect.keys(mapB).forEach(function (key) {
if (!(key in mapA)) {
compMap[key] = mapB[key];
}
});
return compMap;
};
/**
* Asserts that a premise is true.
*/
connect.assertTrue = function (premise, message) {
if (!premise) {
throw new connect.ValueError(message);
}
};
/**
* Asserts that a value is not null or undefined.
*/
connect.assertNotNull = function (value, name) {
connect.assertTrue(value != null && typeof value !== undefined,
connect.sprintf("%s must be provided", name || 'A value'));
return value;
};
connect.deepcopy = function (src) {
return JSON.parse(JSON.stringify(src));
};
connect.deepcopyCrossOriginEvent = function(event) {
const obj = {};
const listOfAcceptableKeys = COPYABLE_EVENT_FIELDS;
listOfAcceptableKeys.forEach((key) => {
try {
obj[key] = event[key];
}
catch(e) {
connect.getLog().info("deepcopyCrossOriginEvent failed on key: ", key).sendInternalLogToServer();
}
});
return connect.deepcopy(obj);
}
/**
* Get the current base url of the open page, e.g. if the page is
* https://example.com:9494/oranges, this will be "https://example.com:9494".
*/
connect.getBaseUrl = function () {
var location = global.location;
return connect.sprintf("%s//%s:%s", location.protocol, location.hostname, location.port);
};
connect.getUrlWithProtocol = function(url) {
var protocol = global.location.protocol;
if (url.substr(0, protocol.length) !== protocol) {
return connect.sprintf("%s//%s", protocol, url);
}
return url;
}
/**
* Determine if the current window is in an iframe.
* Courtesy: http://stackoverflow.com/questions/326069/
*/
connect.isFramed = function () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
};
connect.hasOtherConnectedCCPs = function () {
return connect.numberOfConnectedCCPs > 1;
}
connect.fetch = function (endpoint, options, milliInterval, maxRetry) {
maxRetry = maxRetry || 5;
milliInterval = milliInterval || 1000;
options = options || {};
return new Promise(function (resolve, reject) {
function fetchData(maxRetry) {
fetch(endpoint, options).then(function (res) {
if (res.status === connect.HTTP_STATUS_CODES.SUCCESS) {
res.json().then(json => resolve(json)).catch(() => resolve({}));
} else if (maxRetry !== 1 && (res.status >= connect.HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR || res.status === connect.HTTP_STATUS_CODES.TOO_MANY_REQUESTS)) {
setTimeout(function () {
fetchData(--maxRetry);
}, milliInterval);
} else {
reject(res);
}
}).catch(function (e) {
reject(e);
});
}
fetchData(maxRetry);
});
};
connect.fetchWithTimeout = async function(endpoint, timeoutMs, options, milliInterval, maxRetry) {
options = options || {};
if (!timeoutMs) {
return connect.fetch(endpoint, options, milliInterval, maxRetry);
}
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeoutMs);
const response = await connect.fetch(endpoint, {
...options,
signal: controller.signal
}, milliInterval, maxRetry);
clearTimeout(id);
return response;
}
/**
* Calling a function with exponential backoff with full jitter retry strategy
* It will retry calling the function for maximum maxRetry times if it fails.
* Success callback will be called if the function succeeded.
* Failure callback will be called only if the last try failed.
*/
connect.backoff = function (func, milliInterval, maxRetry, callbacks) {
connect.assertTrue(connect.isFunction(func), "func must be a Function");
var self = this;
var ratio = 2;
func({
success: function (data) {
if (callbacks && callbacks.success) {
callbacks.success(data);
}
},
failure: function (err, data) {
if (maxRetry > 0) {
var interval = milliInterval * 2 * Math.random();
global.setTimeout(function () {
self.backoff(func, interval * ratio, --maxRetry, callbacks);
}, interval);
} else {
if (callbacks && callbacks.failure) {
callbacks.failure(err, data);
}
}
}
});
};
connect.publishMetric = function (metricData) {
connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST, {
event: connect.EventType.CLIENT_METRIC,
data: metricData
});
};
connect.publishSoftphoneStats = function(stats) {
connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST, {
event: connect.EventType.SOFTPHONE_STATS,
data: stats
});
};
connect.publishSoftphoneReport = function(report) {
connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST, {
event: connect.EventType.SOFTPHONE_REPORT,
data: report
});
};
connect.publishClickStreamData = function(report) {
connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST, {
event: connect.EventType.CLICK_STREAM_DATA,
data: report
});
};
connect.publishClientSideLogs = function(logs) {
var bus = connect.core.getEventBus();
bus.trigger(connect.EventType.CLIENT_SIDE_LOGS, logs);
};
connect.addNamespaceToLogs = function(namespace) {
const methods = ['log', 'error', 'warn', 'info', 'debug'];
methods.forEach((method) => {
const consoleMethod = window.console[method];
window.console[method] = function () {
const args = Array.from(arguments);
args.unshift(`[${namespace}]`);
consoleMethod.apply(window.console, args);
};
});
};
/**
* A wrapper around Window.open() for managing single instance popups.
* Tracks window references to enable window reuse without reload and provides security.
*/
connect.PopupManager = function () {
this.windows = {};
this.windowUrls = {};
};
connect.PopupManager.prototype.open = function (url, name, options) {
var features = '';
if (options) {
// default values are chosen to provide a minimum height without scrolling
// and a uniform margin based on the css of the ccp login page
var height = options.height || DEFAULT_POPUP_HEIGHT;
var width = options.width || DEFAULT_POPUP_WIDTH;
var top = options.top || 0;
var left = options.left || 0;
features = "width="+width+", height="+height+", top="+top+", left="+left;
}
var cachedWin = this.windows[name];
var cachedUrl = this.windowUrls[name];
// Check if window exists and is still open
if (cachedWin) {
try {
if (!cachedWin.closed) {
if (cachedUrl === url) {
// Same URL - reuse existing window without reload
try {
cachedWin.focus();
} catch (e) {
// Focus may fail due to browser restrictions, this is acceptable
}
connect.getLog().info("[PopupManager] Reusing existing popup window").sendInternalLogToServer();
return cachedWin;
}
} else {
// Window was closed, clean up tracking
delete this.windows[name];
delete this.windowUrls[name];
}
} catch (e) {
// Can't access window (likely cross-origin), assume closed and clean up
delete this.windows[name];
delete this.windowUrls[name];
}
}
// Open new window directly to URL
var win = window.open(url, name, features);
if (win) {
// Check if window is already closed (edge case in testing scenarios)
try {
if (!win.closed) {
this.windows[name] = win;
this.windowUrls[name] = url;
connect.getLog().info("[PopupManager] Opened popup window").withObject({url: url, name: name}).sendInternalLogToServer();
}
} catch (e) {
// Can't access window properties, but still return it
}
}
return win;
};
connect.PopupManager.prototype.clear = function (name) {
// Clean up window tracking
delete this.windows[name];
delete this.windowUrls[name];
// Clean up localStorage
var key = this._getLocalStorageKey(name);
global.localStorage.removeItem(key);
};
connect.PopupManager.prototype._getLastOpenedTimestamp = function (name) {
var key = this._getLocalStorageKey(name);
var value = global.localStorage.getItem(key);
if (value) {
return parseInt(value, 10);
} else {
return 0;
}
};
connect.PopupManager.prototype._setLastOpenedTimestamp = function (name, ts) {
var key = this._getLocalStorageKey(name);
global.localStorage.setItem(key, '' + ts);
};
connect.PopupManager.prototype._getLocalStorageKey = function (name) {
return "connectPopupManager::" + name;
};
/**
* An enumeration of the HTML5 notification permission values.
*/
var NotificationPermission = connect.makeEnum([
'granted',
'denied',
'default'
]);
/**
* A simple engine for showing notification popups.
*/
connect.NotificationManager = function () {
this.queue = [];
this.permission = NotificationPermission.DEFAULT;
};
connect.NotificationManager.prototype.requestPermission = function () {
var self = this;
if (!("Notification" in global)) {
connect.getLog().warn("This browser doesn't support notifications.").sendInternalLogToServer();
this.permission = NotificationPermission.DENIED;
} else if (global.Notification.permission === NotificationPermission.DENIED) {
connect.getLog().warn("The user has requested to not receive notifications.").sendInternalLogToServer();
this.permission = NotificationPermission.DENIED;
} else if (this.permission !== NotificationPermission.GRANTED) {
global.Notification.requestPermission().then(function (permission) {
self.permission = permission;
if (permission === NotificationPermission.GRANTED) {
self._showQueued();
} else {
self.queue = [];
}
});
}
};
connect.NotificationManager.prototype.show = function (title, options) {
if (this.permission === NotificationPermission.GRANTED) {
return this._showImpl({ title: title, options: options });
} else if (this.permission === NotificationPermission.DENIED) {
connect.getLog().warn("Unable to show notification.")
.sendInternalLogToServer()
.withObject({
title: title,
options: options
});
} else {
var params = { title: title, options: options };
connect.getLog().warn("Deferring notification until user decides to allow or deny.")
.withObject(params)
.sendInternalLogToServer();
this.queue.push(params);
}
};
connect.NotificationManager.prototype._showQueued = function () {
var self = this;
var notifications = this.queue.map(function (params) {
return self._showImpl(params);
});
this.queue = [];
return notifications;
};
connect.NotificationManager.prototype._showImpl = function (params) {
var notification = new global.Notification(params.title, params.options);
if (params.options.clicked) {
notification.onclick = function () {
params.options.clicked.call(notification);
};
}
return notification;
};
connect.ValueError = function () {
var args = Array.prototype.slice.call(arguments, 0);
var format = args.shift();
var instance = new Error(connect.vsprintf(format, args));
Object.setPrototypeOf(instance, connect.ValueError.prototype);
return instance;
};
Object.setPrototypeOf(connect.ValueError.prototype, Error.prototype);
Object.setPrototypeOf(connect.ValueError, Error);
connect.ValueError.prototype.name = 'ValueError';
connect.NotImplementedError = function () {
var args = Array.prototype.slice.call(arguments, 0);
var format = args.shift();
var instance = new Error(connect.vsprintf(format, args));
Object.setPrototypeOf(instance, connect.NotImplementedError.prototype);
return instance;
};
Object.setPrototypeOf(connect.NotImplementedError.prototype, Error.prototype);
Object.setPrototypeOf(connect.NotImplementedError, Error);
connect.NotImplementedError.prototype.name = 'NotImplementedError';
connect.StateError = function () {
var args = Array.prototype.slice.call(arguments, 0);
var format = args.shift();
var instance = new Error(connect.vsprintf(format, args));
Object.setPrototypeOf(instance, connect.StateError.prototype);
return instance;
}
Object.setPrototypeOf(connect.StateError.prototype, Error.prototype);
Object.setPrototypeOf(connect.StateError, Error);
connect.StateError.prototype.name = 'StateError';
connect.VoiceIdError = function(type, message, err){
var error = {};
error.type = type;
error.message = message;
error.stack = Error(message).stack;
error.err = err;
return error;
}
// internal use only
connect.isCCP = function () {
if (!connect.core.upstream) {
return false;
}
var conduit = connect.core.getUpstream();
return conduit.name === 'ConnectSharedWorkerConduit';
}
connect.isSharedWorker = function () {
return connect.worker && !!connect.worker.clientEngine;
}
connect.isCRM = function () {
if (!connect.core.upstream) {
return false;
}
return connect.core.getUpstream() instanceof connect.IFrameConduit || connect.core.getUpstream() instanceof connect.GRProxyIframeConduit;
}
// internal use only
connect.isActiveConduit = function (conduit) {
const grProxyConduit = connect.core.getUpstream();
if (grProxyConduit instanceof connect.GRProxyIframeConduit) {
return conduit.name === grProxyConduit.activeRegionUrl;
} else {
connect.getLog().debug('connect.isActiveConduit is called but there is no GR proxy conduit').sendInternalLogToServer();
return true;
}
}
})();
/***/ },
/***/ 96
() {
/*
* Copyright 2014-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Note: load utils before core.js
*/
(function () {
var global = this || globalThis;
var connect = global.connect || {};
var globalConnect = global.globalConnect || {};
global.connect = connect;
global.globalConnect = globalConnect;
global.lily = connect;
globalConnect.core = {};
var IFRAME_STYLE = "margin: 0; border: 0; padding:0px;width: 0px;height: 0px";
var GLOBALIFRAME_STYLE =
"margin: 0; border: 0; padding:0px;width: 100%;height: 100%";
var GLOBALIFRAME_ID = "globalCCP";
var DIV_DEFAULT_HEIGHT = {
height: "465px",
};
globalConnect.extractCcpRegionParams = function (globalContainerDiv, paramsIn) {
connect.assertNotNull(paramsIn.standByRegion, "ccpBackupResource");
connect.assertNotNull(paramsIn.standByRegion.ccpUrl, "ccpUrl");
connect.assertNotNull(paramsIn.standByRegion.region, "region");
if (paramsIn.pollForFailover) {
connect.assertNotNull(paramsIn.loginUrl, "loginUrl");
connect.assertNotNull(paramsIn.instanceArn, "primary ARN");
paramsIn.otherArn = connect.assertNotNull(paramsIn.standByRegion.instanceArn, "backup ARN");
}
var regionAParams = paramsIn;
var regionBParams = Object.assign({}, paramsIn, {
ccpUrl: paramsIn.standByRegion.ccpUrl,
region: paramsIn.standByRegion.region,
instanceArn: paramsIn.standByRegion.instanceArn,
otherArn: paramsIn.instanceArn,
loginPopup: false
});
var divStyle = extractDivStyle(globalContainerDiv);
if (parseInt(divStyle.height) <= 0) {
globalContainerDiv.style.height = DIV_DEFAULT_HEIGHT.height; // populating ccp
divStyle.height = DIV_DEFAULT_HEIGHT.height;
}
return [regionAParams, regionBParams].map(function (params) {
connect.assertNotNull(params.ccpUrl, "ccpUrl");
connect.assertNotNull(params.region, "region");
delete params.standByRegion;
//signal CCP as part of a disaster recovery fleet
params.disasterRecoveryOn = true;
params.iframe_style = IFRAME_STYLE;
params.height = divStyle.height;
return params;
});
};
var extractDivStyle = function (globalContainerDiv) {
var style = window.getComputedStyle(globalContainerDiv);
return {
height: style.getPropertyValue("height"),
width: style.getPropertyValue("width"),
display: style.getPropertyValue("display"),
};
};
var validateRegion = function (region, availableRegions) {
connect.assertTrue(
typeof region == "string",
"Region provided " + region + " is not a valid string"
);
var regions = availableRegions || globalConnect.core.regions;
if (!regions.hasOwnProperty(region)) {
var message = "Region provided " + region + " is not found!";
throw new connect.ValueError(message);
}
};
/**
* Particular to DR, the getPrimaryRegion paramsIn field is new, and required. It returns a promise that is resolved once the CCP and namespace are successfully initialized.
* It is recommended that you do not attempt to use the connect object before this promise is resolved.
* @param {object} globalContainerDiv -- The container div for the active and secondary CCPs for use with DR.
* @param {object} paramsIn -- Identical to connect.core.initCCP's paramsIn, save for one additional param.
* @param {getPrimaryRegionCallback} paramsIn.getPrimaryRegion -- Required. A callback function that returns a promise that is resolved once the CCP and namespace are successfully initialized.
*/
globalConnect.core.initCCP = function (globalContainerDiv, paramsIn) {
connect.assertNotNull(paramsIn.getPrimaryRegion, "getPrimaryRegion");
connect.assertTrue(
connect.isFunction(paramsIn.getPrimaryRegion),
"getPrimaryRegion must be a function"
);
var getPrimaryRegionFunc = paramsIn.getPrimaryRegion;
delete paramsIn.getPrimaryRegion;
delete paramsIn.provider;
delete paramsIn.plugins;
var dualCcpResources = globalConnect.extractCcpRegionParams(globalContainerDiv, paramsIn);
getPrimaryRegionFunc(
function (primaryRegion) {
return new Promise(resolve => {
var initialRegion;
if (paramsIn.pollForFailover) { // allow getPrimaryRegion function to pass a missing/untruthy region, if polling is enabled
initialRegion = primaryRegion || paramsIn.region;
} else {
initialRegion = connect.assertNotNull(primaryRegion);
}
globalConnect.core.regions = dualCcpResources.reduce(function (obj, resource) {
obj[resource.region] = { ccpParams: resource };
return obj;
}, {});
var arnToRegionMap = dualCcpResources.reduce(function (obj, resource) {
obj[resource.instanceArn] = resource.region;
return obj;
}, {});
validateRegion(initialRegion, globalConnect.core.regions);
globalConnect.core.primaryRegion = initialRegion;
globalConnect.core.secondaryRegion = Object.keys(globalConnect.core.regions).find(function (
region
) {
return region != globalConnect.core.primaryRegion;
});
var containers = dualCcpResources.map(function (resource) {
if (resource.region === globalConnect.core.primaryRegion) {
resource.isPrimary = true;
}
return new globalConnect.Container(resource);
});
//Create global Iframe and attach ccp containers
var ccpIframes = containers.map(function (container) {
return container.ccp.outerHTML;
});
var globalIframe = document.createElement("iframe");
globalIframe.style = GLOBALIFRAME_STYLE;
globalIframe.allow = "microphone; camera; autoplay; clipboard-write; identity-credentials-get";
globalIframe.id = GLOBALIFRAME_ID;
globalIframe.scrolling = "no";
// surface single instance connect api in main window
globalIframe.onload = function () {
activateUI(globalConnect.core.primaryRegion);
deactivateUI(globalConnect.core.secondaryRegion);
containers.map(function (container) {
var regionalFrame = globalIframe.contentDocument.getElementById(container.id);
var contentDocument = regionalFrame.contentDocument;
var contentWindow = regionalFrame.contentWindow;
// inject additionalScripts if specified
if (paramsIn.additionalScripts && Array.isArray(paramsIn.additionalScripts)) {
paramsIn.additionalScripts.forEach(script => {
var scriptElt = contentDocument.createElement('script');
scriptElt.src = script;
contentDocument.body.appendChild(scriptElt);
});
}
// trigger initCCP
contentWindow.init();
var regionalConnect = contentWindow.connect;
globalConnect.core.regions[container.id].connect = regionalConnect;
//listen to failover state change from other window
regionalConnect.core.getUpstream().onUpstream(regionalConnect.DisasterRecoveryEvents.FAILOVER, function (data) {
if (data.nextActiveArn && globalConnect.core.secondaryRegion === arnToRegionMap[data.nextActiveArn]) {
switchDisplayedRegion(arnToRegionMap[data.nextActiveArn], globalConnect.core.primaryRegion);
} else if (data.isPrimary === false && container.id === globalConnect.core.primaryRegion) {
switchDisplayedRegion(globalConnect.core.secondaryRegion, container.id);
} else {
return; // failover request ignored
}
delete globalConnect._failoverPending;
globalConnect._triggerFailoverCompleteHandlers({
activeRegion: globalConnect.core.primaryRegion,
activeCcpUrl: globalConnect.core.regions[globalConnect.core.primaryRegion].ccpParams.ccpUrl
});
});
regionalConnect.core.getUpstream().onUpstream(regionalConnect.DisasterRecoveryEvents.FAILOVER_PENDING, function ({nextActiveArn}) {
if (!globalConnect._failoverPending) {
globalConnect._triggerFailoverPendingHandlers({nextActiveArn});
globalConnect._failoverPending = true;
}
});
if (container.id === globalConnect.core.primaryRegion) {
window.connect = regionalConnect;
}
globalConnect._triggerInitHandlers(regionalConnect, container.id);
});
resolve(globalConnect.core.regions[globalConnect.core.primaryRegion].connect);
};
globalIframe.srcdoc = [
"<!DOCTYPE html>",
"<meta charset='UTF-8'>",
"<html>",
"<head>",
"<style>",
"html, body { width: 100%;height: 100%;margin: 0px;padding: 0px; border: 0px;}",
"</style>",
"</head>",
"<body>",
ccpIframes.join(""),
"</body>",
"</html>",
].join('');
globalContainerDiv.appendChild(globalIframe);
});
},
function (callback) {
console.error(
"[Disaster Recovery] An error occured, while attempting to retrieve your primary region;"
);
callback();
}
);
};
var switchDisplayedRegion = function(newPrimaryRegionId, newSecondaryRegionId) {
globalConnect.core.primaryRegion = newPrimaryRegionId;
globalConnect.core.secondaryRegion = newSecondaryRegionId;
window.connect = globalConnect.core.regions[newPrimaryRegionId].connect;
globalConnect.core.activate(newPrimaryRegionId);
activateUI(newPrimaryRegionId);
deactivateUI(newSecondaryRegionId);
}
var deactivateUI = function (regionID) {
var renderedGlobalIframe = document.getElementById(GLOBALIFRAME_ID);
renderedGlobalIframe.contentDocument.getElementById(regionID).style =
"height: 0; width: 0; border: 0px";
};
var activateUI = function (regionID) {
var renderedGlobalIframe = document.getElementById(GLOBALIFRAME_ID);
renderedGlobalIframe.contentDocument.getElementById(regionID).style =
"height:100%;width:100%;border:0px";
};
/**
* Register a function to be triggered after globalConnect.core.initCCP() is invoked, and once the
* Global Resiliency setup has been successfully initialized on the page and agents are able to begin
* taking contacts. If you wish, you can set up hooks using this function before calling globalConnect.core.initCCP().
*
* @param f A function that will be triggered when the Global Resiliency setup has been
* successfully initialized on the page and agents are able to begin taking contacts.
* The function will be called twice (once for each Connect instance in the setup), with two parameters:
* 1. The Streams API object (the connect object) for one of the Connect instances in the Global Resiliency setup
* 2. A string parameter with the AWS region associated with the Connect instance whose Streams API object
* was provided in the first parameter.
*
* @returns A function that can be called if you wish to deregister the trigger.
*/
globalConnect.core.onInit = function(f) {
globalConnect.core._onInitHandlers = globalConnect.core._onInitHandlers || {};
const subId = connect.randomId();
globalConnect.core._onInitHandlers[subId] = f;
return () => delete globalConnect.core._onInitHandlers[subId];
}
globalConnect._triggerInitHandlers = function(connect, region) {
const handlers = globalConnect.core._onInitHandlers;
if (handlers) {
Object.values(handlers).forEach(f => f(connect, region));
}
}
/**
* Register a function to be triggered when the UI changes to display a different region, and agents are able to
* begin taking contacts in the new CCP region. If automatic region selection is in use for this multi-region setup,
* this function will also be triggered when CCP is initialized and ready for use, if the region provided to the `getPrimaryRegion`
* callback is not the currently active region for the agent.
*
* @param f A function that will be triggered when the UI changes to show CCP for a different region.
* The function will be called with an Object parameter with two properties:
* 1. `activeRegion`: the string name of the AWS region for the newly-active CCP instance
* 2. `activeCcpUrl`: the value of the `ccpUrl` parameter for the newly-active instance,
* as originally provided in the `initCCP()` parameters
*
* @returns A function that can be called if you wish to deregister the trigger.
*/
globalConnect.core.onFailoverComplete = function(f) {
globalConnect.core._failoverCompleteHandlers = globalConnect.core._failoverCompleteHandlers || {};
const subId = connect.randomId();
globalConnect.core._failoverCompleteHandlers[subId] = f;
return () => delete globalConnect.core._failoverCompleteHandlers[subId];
}
globalConnect._triggerFailoverCompleteHandlers = function(data) {
const handlers = globalConnect.core._failoverCompleteHandlers;
if (handlers) {
Object.values(handlers).forEach(f => f(data));
}
}
/**
* Register a function to be triggered when an active region change has been detected, when soft failover is enabled and
* a voice contact is active. The UI will wait to change over to the new region until the active voice contact is destroyed.
*
* @param f A function that will be triggered when a soft failover has been scheduled to occur when the active voice contact is destroyed.
* The function will be called with an Object parameter with one property:
* 1. `nextActiveArn`: the ARN of the Connect instance that will become active in the UI once the active voice contact is destroyed.
*
* @returns A function that can be called if you wish to deregister the trigger.
*/
globalConnect.core.onFailoverPending = function(f) {
globalConnect.core._failoverPendingHandlers = globalConnect.core._failoverPendingHandlers || {};
const subId = connect.randomId();
globalConnect.core._failoverPendingHandlers[subId] = f;
return () => delete globalConnect.core._failoverPendingHandlers[subId];
}
globalConnect._triggerFailoverPendingHandlers = function(data) {
const handlers = globalConnect.core._failoverPendingHandlers;
if (handlers) {
Object.values(handlers).forEach(f => f(data));
}
}
/**
* Download CCP agent logs from the CCP instances in this multi-region setup. A separate log file
* will be produced for each instance. The options are the same as for `connect.getLog().download()`,
* except each log name will be prefixed with the AWS region associated with that log's Connect instance.
*
* @param options Optional parameter of type Object, providing Download options:
* { logName: 'agent-log', // (the default name)
* filterByLogLevel: false // download all logs (the default)
* }
* e.g. in a multi-region setup with one CCP instance in us-west-2 and another in us-east-1, this will
* download two files: us-west-2-agent-log.txt and us-east-1-agent-log.txt.
*/
globalConnect.core.downloadLogs = function(options) {
if (globalConnect.core.regions && globalConnect.core.regions[globalConnect.core.primaryRegion]
&& globalConnect.core.regions[globalConnect.core.primaryRegion].connect) {
Object.entries(globalConnect.core.regions).forEach(([region, {connect}]) => {
const logName = `${region}-${options && options.logName || 'agent-log'}`;
connect.getLog().download({logName, filterByLogLevel: options && options.filterByLogLevel});
});
} else {
throw new Error("CCP is not initialized yet. Please call initCCP() first and wait until the getPrimaryRegion promise resolves.");
}
};
globalConnect.core.failover = function (useSoftFailover) {
globalConnect.core.failoverTo(globalConnect.core.secondaryRegion, useSoftFailover);
};
globalConnect.core.failoverTo = function (electedNewPrimaryRegion, useSoftFailover) {
validateRegion(electedNewPrimaryRegion);
if (electedNewPrimaryRegion === globalConnect.core.primaryRegion) {
connect
.getLog()
.info(`[Disaster Recovery] Ignoring request to fail over to region ${electedNewPrimaryRegion} since it is already the currently active region.`)
.sendInternalLogToServer();
} else {
globalConnect.core.deactivate(globalConnect.core.primaryRegion, useSoftFailover);
}
};
/**-------------------------------------------------------------------------
* Deactivates a region
*/
globalConnect.core.deactivate = function (region, useSoftFailover) {
var connect = globalConnect.core.regions[region].connect;
connect
.getLog()
.info("[Disaster Recovery] Deactivating %s region.", region)
.sendInternalLogToServer();
// call this to suppress contacts
if (connect.core.suppressContacts && connect.core.forceOffline) {
connect.core.suppressContacts(true);
connect.core.forceOffline({softFailover: useSoftFailover});
} else {
connect.getLog().error("[Disaster Recovery] CCP did not load successfully for region %s; unable to deactivate region", region);
}
};
/**-------------------------------------------------------------------------
* Activates Stand-by region on failover using suppress==false event
*/
globalConnect.core.activate = function (region) {
var connect = globalConnect.core.regions[region].connect;
connect
.getLog()
.info("[Disaster Recovery] Activating %s region.", region)
.sendInternalLogToServer();
if (connect.core.suppressContacts) {
connect.core.suppressContacts(false);
} else {
connect.getLog().error("[Disaster Recovery] CCP did not load successfully for region %s; unable to activate region", region);
}
};
})();
/***/ },
/***/ 236
() {
/*
* JavaScript MD5
* https://github.com/blueimp/JavaScript-MD5
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* https://opensource.org/licenses/MIT
*
* Based on
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/* global define */
/* eslint-disable strict */
;(function ($) {
var ctx = this || globalThis;
/**
* Add integers, wrapping at 2^32.
* This uses 16-bit operations internally to work around bugs in interpreters.
*
* @param {number} x First integer
* @param {number} y Second integer
* @returns {number} Sum
*/
function safeAdd(x, y) {
var lsw = (x & 0xffff) + (y & 0xffff)
var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
return (msw << 16) | (lsw & 0xffff)
}
/**
* Bitwise rotate a 32-bit number to the left.
*
* @param {number} num 32-bit number
* @param {number} cnt Rotation count
* @returns {number} Rotated number
*/
function bitRotateLeft(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt))
}
/**
* Basic operation the algorithm uses.
*
* @param {number} q q
* @param {number} a a
* @param {number} b b
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5cmn(q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5ff(a, b, c, d, x, s, t) {
return md5cmn((b & c) | (~b & d), a, b, x, s, t)
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5gg(a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & ~d), a, b, x, s, t)
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5hh(a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t)
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5ii(a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | ~d), a, b, x, s, t)
}
/**
* Calculate the MD5 of an array of little-endian words, and a bit length.
*
* @param {Array} x Array of little-endian words
* @param {number} len Bit length
* @returns {Array<number>} MD5 Array
*/
function binlMD5(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << len % 32
x[(((len + 64) >>> 9) << 4) + 14] = len
var i
var olda
var oldb
var oldc
var oldd
var a = 1732584193
var b = -271733879
var c = -1732584194
var d = 271733878
for (i = 0; i < x.length; i += 16) {
olda = a
oldb = b
oldc = c
oldd = d
a = md5ff(a, b, c, d, x[i], 7, -680876936)
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
b = md5gg(b, c, d, a, x[i], 20, -373897302)
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)
a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
d = md5hh(d, a, b, c, x[i], 11, -358537222)
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)
a = md5ii(a, b, c, d, x[i], 6, -198630844)
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)
a = safeAdd(a, olda)
b = safeAdd(b, oldb)
c = safeAdd(c, oldc)
d = safeAdd(d, oldd)
}
return [a, b, c, d]
}
/**
* Convert an array of little-endian words to a string
*
* @param {Array<number>} input MD5 Array
* @returns {string} MD5 string
*/
function binl2rstr(input) {
var i
var output = ''
var length32 = input.length * 32
for (i = 0; i < length32; i += 8) {
output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff)
}
return output
}
/**
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*
* @param {string} input Raw input string
* @returns {Array<number>} Array of little-endian words
*/
function rstr2binl(input) {
var i
var output = []
output[(input.length >> 2) - 1] = undefined
for (i = 0; i < output.length; i += 1) {
output[i] = 0
}
var length8 = input.length * 8
for (i = 0; i < length8; i += 8) {
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32
}
return output