diff --git a/Documentation-DR.md b/Documentation-DR.md new file mode 100644 index 00000000..e2eabdbe --- /dev/null +++ b/Documentation-DR.md @@ -0,0 +1,362 @@ +# Global Resiliency/Disaster Recovery Streams + +**In order to obtain access to the Global Resiliency feature, you will need to reach out to your Amazon Connect Solutions Architect or Technical Account Manager first.** + +**Global Resiliency is only compatible with CCPv2 and Streams releases 2.6.0 or later, and requires the use of SAML authentication. Global Resiliency can’t be used with CCPv1 or with Connect instances configured to use Connect-managed authentication (username and password).** + +To enable embedded or custom CCP to redirect new inbound contacts to the current active region for an agent, you will need to integrate with the new StreamsJS library specifically designed for use with traffic distribution groups. The new Streams library release can only be used with a traffic distribution group and does not replace the existing Streams library (connect-streams.js or connect-streams-min.js) used in a single region/Connect instance. The new Streams API is the same as the one in a single region/instance Streams library, but instead of embedding only one CCP, you will configure information for CCPs corresponding to both source and replica instances in your traffic distribution group. + +Additionally, the new Streams library suppresses contacts from the Connect instance in the region where the agent is not currently active. In embedded use cases where the native CCP UI will be visible, Streams will show only the CCP for the region where the agent is active. In the event of a change to the agent’s active region, Streams will automatically switch over the embedded UI to display CCP for the newly active region, and hide the CCP UI for the region where the agent was previously active. + +# Prerequisites + +You will need to complete all prerequisites in the [Global Resiliency documentation](https://docs.aws.amazon.com/connect/latest/adminguide/get-started-connect-global-resiliency.html) before you can make use of the version of Streams that is compatible with the feature. + +# Usage + +amazon-connect-streams is available from [npmjs.com](https://www.npmjs.com/package/amazon-connect-streams). If you'd like to download it here, you can use either of the files like `release/connect-streams-dr*`. + +## Caveat regarding region-down scenarios + +When there is a change made to the AWS region in which an agent is active, the agent's embedded Streams setup will only be automatically switched to the new region once their current voice contact (if any) is destroyed and cleared. If there is an impairment to the agent's currently-active region such that they cannot successfully end and clear their ongoing voice contact, it may be necessary for agents to refresh the page where CCP is embedded, in order to switch to the new region after traffic has shifted away from the impaired region. + +Additionally, if the active region is already impaired at the time the agent loads the page where CCP is embedded, Streams may be unable to automatically detect a change in active region for the agent. In this case, the agent can refresh (or close and reopen) their open tab where CCP is embedded, and the new active region will be picked up when the page finishes loading. + +## Getting Started + +### Allowlisting + +The first step to using Streams is to allowlist the pages you wish to embed. For our customers’ security, we require that all domains which embed the CCP for a particular instance are explicitly allowlisted. Each domain entry identifies the protocol scheme, host, and port. Any pages hosted behind the same protocol scheme, host, and port will be allowed to embed the CCP components which are required to use the Streams library. + +**For Global Resiliency, you'll need to complete the below allowlisting process separately for each instance in your traffic distribution group.** + +To allowlist your pages: + +1. Login to your AWS Account, then navigate to the Amazon Connect console in each region where you have an instance in your traffic distribution group. +2. Click the instance name of the instance for which you would like to allowlist pages to load the settings page for your instance. +3. Click the "Application integration" link on the left. +4. Click "+ Add Origin", then enter a domain URL, e.g. "https://example.com", or "https://example.com:9595" if your website is hosted on a non-standard port. + +#### A few things to note: + +* Allowlisted domains must be HTTPS. +* All of the pages that attempt to initialize the Streams library must be hosted on domains that are allowlisted as per the above steps. +* All open tabs that contain an initialized Streams library or any other CCP tabs opened will be synchronized. This means that state changes made in one open window will be communicated to all open windows. +* Using multiple browsers at the same time for the same Connect instance is not supported, and causes issues with the rtc communication. + +## Downloading Streams with npm + +`npm install amazon-connect-streams` + +## Importing Streams with npm and ES6 + +`import "amazon-connect-streams/connect-streams-dr.js";` This will make the `globalConnect` variable available in the current context. + +## Downloading Streams from Github + +You can also download prebuilt release artifacts directly from GitHub. You will find the release artifacts `connect-streams-dr.js` and the minified version `connect-streams-dr-min.js` in the `release` directory of this repository. + +## Build your own with NPM + +## Install latest LTS version of [NodeJS](https://nodejs.org/) + +``` +$ git clone https://github.com/aws/amazon-connect-streams +$ cd amazon-connect-streams +$ npm install +$ npm run release + +``` + +Find build artifacts in **release** directory - This will generate a file called `connect-streams-dr.js` and the minified version `connect-streams-dr-min.js` - this is the Global Resiliency-aware Connect Streams artifact which you will include in your page. +To run unit tests: + +``` +$ npm run test-mocha +$ npm run test-mocha-dr + +``` + +Note: these tests run on the release files generated above. + +## Using the standard Streams artifact alongside the Global Resiliency Streams artifact + +The Global Resiliency Streams artifact uses the `window.connect` binding to provide an easy way to make Connect API calls to the CCP for the agent's currently active region. It is possible but not recommended to load both artifacts in the same browsing context, since the global Streams artifact will overwrite the `window.connect` binding when first loaded on the page, when `globalConnect.core.initCCP()` is called, and each time an active region change occurs. + +## Initialization + +Initializing the Streams API is the first step to verify that you have everything set up correctly and that you will be able to listen for events. + +### `globalConnect.core.initCCP()` + +``` + + +
+ + + + + + + + + + +``` + +Integrates with Connect by loading the pre-built CCPs located at `ccpUrl` and `standByRegion.ccpUrl` into iframes and placing them into the `containerDiv` provided. API requests are funneled through these CCPs, and agent and contact updates are published through them and made available to your JS client code. + +* `ccpUrl`: The URL of the CCP for one of the two regions in your traffic distribution group. This is the page you would normally navigate to in order to use the CCP in a standalone page, it is different for each instance. +* `pollForFailover`: Required, must be set to true in order to activate Global Resiliency feature in Streams. +* `instanceArn`: Required, must be the ARN for the instance whose URL you provided in `ccpUrl` +* `loginUrl`: Required. Allows custom URL to be used to initiate the CCP, as in the case of SAML authentication. Please follow the guide in the Amazon Connect Administrator Guide to set up global authentication and integrate your IdP with the new Connect SAML endpoint. +* `region`: Required. Amazon Connect instance region for the instance provided in `ccpUrl`. ex: `us-east-1`. +* `standByRegion`: Required; provides information for the other instance in the traffic distribution group. + * `ccpUrl`: The URL of the CCP for the other instance in the traffic distribution group. + * `instanceArn`: Required, must be the ARN for the instance whose URL you provided in `standByRegion.ccpUrl` + * `region`: Required. Amazon Connect instance region for the instance provided in `standByRegion.ccpUrl`. ex: `us-west-2`. +* `getPrimaryRegion`: Required. Specify a function that will receive a callback as an argument; when you invoke the callback function, it will return a promise that will resolve when the Global Resiliency setup on the page is set up and ready for use. +* `additionalScripts`: Optional. Provides a way to pass additional JavaScript scripts to be loaded for each regional CCP in the Global Resiliency setup that rely on the Connect object (such as ChatJS or TaskJS). If provided, should be an array of relative or absolute URIs that locate scripts to be loaded and executed for each CCP before it is initialized. +* `loginPopup`: Optional, defaults to `true`. Set to `false` to disable the login popup which is shown when the user's authentication expires. +* `loginOptions`: Optional, only valid when `loginPopup` is set to `true`. Provide an object with the following properties to open loginpopup in a new window instead of a new tab. + * `autoClose`: Optional, defaults to `false`. Set to `true` to automatically close the login popup after the user logs in. + * `height`: This allows you to define the height of the login pop-up window. + * `width`: This allows you to define the width of the login pop-up window. + * `top`: This allows you to define the top of the login pop-up window. + * `left`: This allows you to define the left of the login pop-up window. +* `loginPopupAutoClose`: Optional, defaults to `false`. Set to `true` in conjunction with the `loginPopup` parameter to automatically close the login Popup window once the authentication step has completed. If the login page opened in a new tab, this parameter will also auto-close that tab. This can also be set in `loginOptions` if those options are used. +* `softphone`: This object is optional and allows you to specify some settings surrounding the softphone feature of Connect. + * `allowFramedSoftphone`: Normally, the softphone microphone and speaker components are not allowed to be hosted in an iframe. This is because the softphone must be hosted in a single window or tab. The window hosting the softphone session must not be closed during the course of a softphone call or the call will be disconnected. If `allowFramedSoftphone` is `true`, the softphone components will be allowed to be hosted in this window or tab. + * `disableRingtone`: This option allows you to completely disable the built-in ringtone audio that is played when a call is incoming. + * `ringtoneUrl`: If the ringtone is not disabled, this allows for overriding the ringtone with any browser-supported audio file accessible by the user. +* `pageOptions`: This object is optional and allows you to configure which configuration sections are displayed in the settings tab. + * `enableAudioDeviceSettings`: If `true`, the settings tab will display a section for configuring audio input and output devices for the agent's local machine. If `false`, or if `pageOptions` is not provided, the agent will not be able to change audio device settings from the settings tab. + * `enablePhoneTypeSettings`: If `true`, or if `pageOptions` is not provided, the settings tab will display a section for configuring the agent's phone type and deskphone number. If `false`, the agent will not be able to change the phone type or deskphone number from the settings tab. +* `shouldAddNamespaceToLogs`: prepends `[CCP]` to all logs logged by the CCP. Important note: there are a few logs made by the CCP before the namespace is prepended. +* `ccpAckTimeout`: A timeout in ms that indicates how long streams will wait for the iframed CCP to respond to its `SYNCHRONIZE` event emissions. These happen continuously from the first time `initCCP` is called. They should only appear when there is a problem that requires a refresh or a re-login. +* `ccpSynTimeout`: A timeout in ms that indicates how long streams will wait to send a new `SYNCHRONIZE` event to the iframed CCP. These happens continuously from the first time `initCCP` is called. +* `ccpLoadTimeout`: A timeout in ms that indicates how long streams will wait for the initial `ACKNOWLEDGE` event from the shared worker while the CCP is still standing itself up. + +#### A few things to note: + +* You have the option to show or hide the pre-built UI by showing or hiding the `containerDiv` into which you place the iframe, or applying a CSS rule like this: + +``` +#container-div iframe { + display: none; +} +``` + +* The CCP UI is rendered in an iframe under the container element provided. The iframe fills its container element with `width: 100%; height: 100%`. To customize the size of the CCP, set the width and height for the container element. +* The CCP is designed to be responsive (used in various sizes). The smallest size we design for is 320px x 460px. For a good user experience, we recommend that you do not go smaller than this size. +* CSS styles you add to your site will NOT be applied to the CCP because it is rendered in an iframe. + +## How to make Streams API calls to the CCP for the currently-active region + +The `window.connect` reference on the page will be updated to point to the Connect API object for the currently-active region, both at initialization time and each time there is a change in active region on the page. You can refer to the [standard documentation](https://code.amazon.com/packages/AmazonConnectStreams/blobs/d226dd7efac909181ad31f0759e10593bad9fc4d/--/Documentation.md) for reference on what APIs are available to use on this object. You will need to wait for the `getPrimaryRegion` promise to resolve before you will have a Connect object available for use on the page. + +If you have a standard set of Connect API calls (e.g. common `onConnecting()` hooks, usage of `connect.agent()` or `connect.contact()`) you would typically make to set up the embedded CCP on the page, in order to ensure they’re applied to both instances on the page, you should make these API calls in a `globalConnect.core.onInit()` hook. Your onInit logic will be invoked for each Connect instance in the Global Resiliency setup. + + +``` +globalConnect.core.onInit((connect, region) => { + connect.contact((contact) => { + contact.onConnecting((contact) => { + console.log(`contact with ID ${contact.getContactId()} connecting in region ${region}`); + }); + }); +}); +globalConnect.core.initCCP(containerDiv, { + ... +} +``` + +## Loading other JavaScript that modifies the Streams API (e.g. ChatJS, TaskJS) + +Since the full `window.connect` binding will not be available until the Global Resiliency setup is initialized on the page, code that relies on modifying the Connect Streams object, such as ChatJS, TaskJS, and other custom code you may have written to work with the standard (non-Global Resiliency) Streams distribution should be loaded in your code that handles the promise returned by your function passed as the getPrimaryRegion parameter of `globalConnect.core.initCCP()`, instead of being loaded immediately at page load time along with Streams itself, as with the non-Global Resiliency version of Streams. + +Any scripts loaded this way should also be loaded as part of a `globalConnect.core.onFailoverCompleted()` hook, to ensure that the code will apply to the newly-active CCP in the event of an active region change; otherwise the code would be applied only to the CCP for the region that was originally active. + + +``` +globalConnect.core.onFailoverCompleted(() => { + const script = document.createElement('script'); + script.src = "https://example.com/amazon-connect-chat.js"; + document.body.appendChild(script); +}); +globalConnect.core.initCCP(containerDiv, {ccpUrl: ">16&255,u[l++]=t>>8&255,u[l++]=255&t;return 2===a&&(t=o[e.charCodeAt(n)]<<2|o[e.charCodeAt(n+1)]>>4,u[l++]=255&t),1===a&&(t=o[e.charCodeAt(n)]<<10|o[e.charCodeAt(n+1)]<<4|o[e.charCodeAt(n+2)]>>2,u[l++]=t>>8&255,u[l++]=255&t),u},n.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],s=16383,a=0,c=n-o;a >16&255,u[l++]=t>>8&255,u[l++]=255&t;return 2===a&&(t=o[e.charCodeAt(n)]<<2|o[e.charCodeAt(n+1)]>>4,u[l++]=255&t),1===a&&(t=o[e.charCodeAt(n)]<<10|o[e.charCodeAt(n+1)]<<4|o[e.charCodeAt(n+2)]>>2,u[l++]=t>>8&255,u[l++]=255&t),u},n.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],s=16383,a=0,c=n-o;a=S&&I("invalid-input"),((d=(y=e.charCodeAt(o++))-48<10?y-22:y-65<26?y-65:y-97<26?y-97:u)>=u||d>C((c-b)/s))&&I("overflow"),b+=d*s,!(d<(h=a<=_?l:a>=_+p?p:a-_));a+=u)s>C(c/(v=u-h))&&I("overflow"),s*=v;_=L(b-i,t=E.length+1,0==i),C(b/t)>c-T&&I("overflow"),T+=C(b/t),b%=t,E.splice(b++,0,T)}return R(E)}function O(e){var t,n,r,o,i,s,a,d,h,v,y,E,S,b,_,A=[];for(E=(e=w(e)).length,t=g,n=0,i=f,s=0;s=0&&delete e.httpRequest.headers["Content-Length"]}}},{"../json/builder":37,"../json/parser":38,"../util":74,"./json":48,"./rest":50}],52:[function(e,t,n){var r=e("../core"),o=e("../util"),i=e("./rest");t.exports={buildRequest:function(e){i.buildRequest(e),["GET","HEAD"].indexOf(e.httpRequest.method)<0&&function(e){var t=e.service.api.operations[e.operation].input,n=new r.XML.Builder,i=e.params,s=t.payload;if(s){var a=t.members[s];if(void 0===(i=i[s]))return;if("structure"===a.type){var c=a.name;e.httpRequest.body=n.toXML(i,a,c,!0)}else e.httpRequest.body=i}else e.httpRequest.body=n.toXML(i,t,t.name||t.shape||o.string.upperFirst(e.operation)+"Request")}(e)},extractError:function(e){var t;i.extractError(e);try{t=(new r.XML.Parser).parse(e.httpResponse.body.toString())}catch(n){t={Code:e.httpResponse.statusCode,Message:e.httpResponse.statusMessage}}t.Errors&&(t=t.Errors),t.Error&&(t=t.Error),t.Code?e.error=o.error(new Error,{code:t.Code,message:t.Message}):e.error=o.error(new Error,{code:e.httpResponse.statusCode,message:null})},extractData:function(e){var t;i.extractData(e);var n=e.request,s=e.httpResponse.body,a=n.service.api.operations[n.operation],c=a.output,u=(a.hasEventOutput,c.payload);if(u){var l=c.members[u];l.isEventStream?(t=new r.XML.Parser,e.data[u]=o.createEventStream(2===r.HttpClient.streamsApiVersion?e.httpResponse.stream:e.httpResponse.body,t,l)):"structure"===l.type?(t=new r.XML.Parser,e.data[u]=t.parse(s.toString(),l)):"binary"===l.type||l.isStreaming?e.data[u]=s:e.data[u]=l.toType(s)}else if(s.length>0){var p=(t=new r.XML.Parser).parse(s.toString(),c);o.update(e.data,p)}}}},{"../core":19,"../util":74,"./rest":50}],53:[function(e,t,n){var r=e("../util");function o(){}function i(e){return e.isQueryName||"ec2"!==e.api.protocol?e.name:e.name[0].toUpperCase()+e.name.substr(1)}function s(e,t,n,o){r.each(n.members,(function(n,r){var s=t[n];if(null!=s){var c=i(r);a(c=e?e+"."+c:c,s,r,o)}}))}function a(e,t,n,o){null!=t&&("structure"===n.type?s(e,t,n,o):"list"===n.type?function(e,t,n,o){var s=n.member||{};0!==t.length?r.arrayEach(t,(function(t,r){var c="."+(r+1);if("ec2"===n.api.protocol)c+="";else if(n.flattened){if(s.name){var u=e.split(".");u.pop(),u.push(i(s)),e=u.join(".")}}else c="."+(s.name?s.name:"member")+c;a(e+c,t,s,o)})):o.call(this,e,null)}(e,t,n,o):"map"===n.type?function(e,t,n,o){var i=1;r.each(t,(function(t,r){var s=(n.flattened?".":".entry.")+i+++".",c=s+(n.key.name||"key"),u=s+(n.value.name||"value");a(e+c,t,n.key,o),a(e+u,r,n.value,o)}))}(e,t,n,o):o(e,n.toWireFormat(t).toString()))}o.prototype.serialize=function(e,t,n){s("",e,t,n)},t.exports=o},{"../util":74}],54:[function(e,t,n){t.exports={now:function(){return"undefined"!=typeof performance&&"function"==typeof performance.now?performance.now():Date.now()}}},{}],55:[function(e,t,n){t.exports={isFipsRegion:function(e){return"string"==typeof e&&(e.startsWith("fips-")||e.endsWith("-fips"))},isGlobalRegion:function(e){return"string"==typeof e&&["aws-global","aws-us-gov-global"].includes(e)},getRealRegion:function(e){return["fips-aws-global","aws-fips","aws-global"].includes(e)?"us-east-1":["fips-aws-us-gov-global","aws-us-gov-global"].includes(e)?"us-gov-west-1":e.replace(/fips-(dkr-|prod-)?|-fips/,"")}}},{}],56:[function(e,t,n){var r=e("./util"),o=e("./region_config_data.json");function i(e,t){r.each(t,(function(t,n){"globalEndpoint"!==t&&(void 0!==e.config[t]&&null!==e.config[t]||(e.config[t]=n))}))}t.exports={configureEndpoint:function(e){for(var t=function(e){var t=e.config.region,n=function(e){if(!e)return null;var t=e.split("-");return t.length<3?null:t.slice(0,t.length-2).join("-")+"-*"}(t),r=e.api.endpointPrefix;return[[t,r],[n,r],[t,"*"],[n,"*"],["*",r],["*","*"]].map((function(e){return e[0]&&e[1]?e.join("/"):null}))}(e),n=e.config.useFipsEndpoint,r=e.config.useDualstackEndpoint,s=0;s=S&&I("invalid-input"),((d=(y=e.charCodeAt(o++))-48<10?y-22:y-65<26?y-65:y-97<26?y-97:u)>=u||d>T((c-b)/s))&&I("overflow"),b+=d*s,!(d<(h=a<=A?l:a>=A+p?p:a-A));a+=u)s>T(c/(v=u-h))&&I("overflow"),s*=v;A=L(b-i,t=E.length+1,0==i),T(b/t)>c-C&&I("overflow"),C+=T(b/t),b%=t,E.splice(b++,0,C)}return R(E)}function O(e){var t,n,r,o,i,s,a,d,h,v,y,E,S,b,A,_=[];for(E=(e=w(e)).length,t=g,n=0,i=f,s=0;s