Skip to content

Commit

Permalink
Merge branch 'development' version 0.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
atoa committed Jul 7, 2017
2 parents 515fbbf + 7285226 commit 8979ff2
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 70 deletions.
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.6.0] - 2017-07-07
### Added
- Added the ability to pass dynamic configuration from parent page to the
bot loader via an event
- Added response cards object display to sample parent page

### Changed
- Bot loader script now uses its own credential variable instead of setting
it into the global AWS object
- Bumped AWS SDK version in bot loader
- Added functionality to remove event handlers in bot loader for events that
only fire once

### Fixed
- Typos, invalid links and display issues in README files

## [0.5.2] - 2017-07-05
### Fixed
- Credential loading issue in parent bot-loader.js

## [0.5.1] - 2017-06-06
### Changed
- Copyrights and Amazon software license

## [0.5.0] - 2017-06-05
### Added
- Ability to deploy a sample bot based on the OrderFlowers sample
Expand Down
4 changes: 2 additions & 2 deletions lex-web-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ environment. The files follow this directory structure:

Here's an example of the `config.dev.json` file:

```json
```
{
"cognito": {
"poolId": "us-east-1:deadbeef-cac0-babe-abcd-abcdef01234",
Expand Down Expand Up @@ -168,7 +168,7 @@ played back. The chatbot UI provides options to control the playback.
For example, you can allow to interrupt the playback of long responses and
fine tune the various values associated with interruptions:

```json
```
...
lex: {
Expand Down
2 changes: 1 addition & 1 deletion lex-web-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lex-web-ui",
"version": "0.5.1",
"version": "0.6.0",
"description": "Lex ChatBot Web Interface",
"author": "AWS",
"license": "Amazon Software License",
Expand Down
124 changes: 85 additions & 39 deletions lex-web-ui/static/iframe/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
###Overview
### Overview
The chatbot UI can be embedded in an existing web site by loading it as
an iframe. This includes embedding it in a cross-origin setup where the
chatbot is served from a server, S3 bucket or CloudFront distribution
in a domain that is different from the hosting web site.

If you want to know more about the chatbot UI component, please refer to
its [README](https://github.com/awslabs/aws-lex-web-ui/lex-web-ui) file.
its [README](https://github.com/awslabs/aws-lex-web-ui/blob/master/lex-web-ui/README.md) file.

###Adding the ChatBot UI to your Website
### Adding the ChatBot UI to your Website
This project provides a sample JavaScript loader
[bot-loader.js](./bot-loader.js") and CSS file [bot.css](./bot.css)
that can be used to add the chatbot to an existing web site using a
Expand All @@ -22,12 +22,12 @@ tags to your web page:
<script src="https://myboturl.example.com/static/iframe/bot-loader.js"></script>
```

###Passing Data Between Parent and ChatBot UI
The chatbot iframe supports passing data to and from the hosting site. This
is done using the JavaScript
### Passing Data Between Parent Page and ChatBot UI
The chatbot iframe supports passing data to and from the hosting parent
page. This is done using the JavaScript
[postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
call. This enables use cases such as passing credentials from
the parent hosting site to the chat iframe or passing events (e.g.
call. This mechanism enables use cases such as passing credentials
from the parent hosting site to the chat iframe or passing events (e.g.
windows resize) from the chat window to the parent window.

The chatbot iframe sends Lex bot state update events to the parent
Expand All @@ -36,18 +36,32 @@ of the bot. The bot-loader.js script relays these messages back to the
parent by emitting the `updatelexstate` events. The event object will
contain the Lex state variables in the `details.state` field.

###Configuration
####Parent Configuration
The parent page configuration is held in a JSON config file:
[config.json](./config.json). This file is loaded by
the bot-loader.js script. Here's an example of the file format:
```json
### Configuration
#### Parent Configuration File
The [bot-loader.js](./bot-loader.js) script loads its initial
configuration from the JSON config file: [config.json](./config.json).
This file is meant to be used as the build-time configuration of the
bot-loader.js script. It serves as the base config so the root level
keys in the JSON object should not be removed.

NOTE: The values in this file may be overwritten by environmental
variables in the build process.

Here's an example of the file format:
```
{
// iframe origin - see: Cross Origin Configuration section below
"iframeOrigin": "http://localhost:8080",
// time to wait for the config event in ms
"configEventTimeOutInMs": 10000,
// used to initialize the AWS SDK and Cognito
"aws": {
"cognitoPoolId": "us-east-1:deadbeef-cac0-babe-abcd-abcdef01234",
"region": "us-east-1"
}
// chatbot UI configuration passed from parent - see: ChatBot UI Configuration section below
"iframeConfig": {
...
"lex": {
Expand All @@ -59,35 +73,67 @@ the bot-loader.js script. Here's an example of the file format:
}
```

####ChatBot UI Configuration
The chatbot UI has a local build-time config (see:
`src/config/config.prod.json`). You can also pass or override this
configuration from the parent site via two mechanisms:

- **ChatBot UI Configuration from File.** The parent
[config.json](./config.json) file contains the `iframeConfig` field
which is passed to the chatbot UI. This configuration is dynamically
sent to the chatbot UI as a response of the the `onInitIframeConfig`
event. The values delivered via this mechanism override the chatbot
ui local config files and URL config parameter.
- **ChatBot Configuration form URL Parameter.** The chatbot UI
configuration can be initialized using the `config` URL parameter. Your
application can dynamically add the parameter to the URL This is supported
both in iframe and stand-alone mode of the chatbot UI. This config URL
parameter should follow the same JSON structure of the `configDefault`
object in the `src/config/index.js` file. This parameter should be a JSON
serialized and URL encoded string. Values from this parameter override
the ones from the chatbot ui local config files. For example to change
the initialText config field, you can use a URL like this:
`https://mybucket.s3.amazonaws.com/index.html#/?config=%7B%22lex%22%3A%7B%22initialText%22%3A%22Ask%20me%20a%20question%22%7D%7D`

####Cross Origin Configuration
#### Parent Event Configuration
The parent page can also set the bot loader configuration via an
event. The bot-loader.js script emits the `receivelexconfig` event which
signals to the parent that it is ready to receive a configuration
object. At which point, the bot-loader.js script will wait a 10
seconds timeout (by default) to receive a event named `loadlexconfig`.
The timeout is controlled by the `configEventTimeOutInMs` field in
the config JSON file. The event object contains the config in the
`detail.config` field.

The configuration from the JSON file is merged with the value for this
event. The values received via this event take precedence over the
JSON file.

For example, to pass the browser
[user agent](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorID/userAgent),
you can add code to your site along the lines of:
```javascript
document.addEventListener('receivelexconfig', onReceiveLexConfig, false);

function onReceiveLexConfig() {
document.removeEventListener('receivelexconfig', onReceiveLexConfig, false);
var config = {
iframeConfig: {
lex: {
sessionAttributes: {
userAgent: navigator.userAgent,
},
},
},
};

var event = new CustomEvent('loadlexconfig', { detail: { config: config } });
document.dispatchEvent(event);
}
```

#### ChatBot UI Configuration
The chatbot UI has its own configuration (see the
[README](https://github.com/awslabs/aws-lex-web-ui/blob/master/lex-web-ui/README.md#configuration-and-customization)
for details. You can also pass or override the chatbot UI configuration
from the parent site via the following mechanisms:

1. **Config Object.** The parent configuration config object (either from
the [config.json](./config.json) file or passed via the `loadlexconfig`
event) contains the `iframeConfig` field which is passed to the chatbot
UI. This configuration is dynamically sent to the chatbot UI as a
response of the the `onInitIframeConfig` event. The values delivered
via this mechanism override the chatbot UI local config files and URL
config parameter.
2. **URL Parameter.** The chatbot UI configuration can be initialized using
the `config` URL parameter. For details, see the
[URL Parameter](https://github.com/awslabs/aws-lex-web-ui/blob/master/lex-web-ui/README.md#url-parameter)
section. NOTE: the bot-loader.js script does not use URL parameters.

#### Cross Origin Configuration
If the chatbot UI is hosted on a different
[Origin](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)
from the parent window, you need to configure the `iframeOrigin` field
in the parent config.json file to point to the origin of the iframe. This
in the parent `config.json` file to point to the origin of the iframe. This
origin configuration is used to control which sites can communicate with
the iframe. Conversely, you would need to configure the `ui.parentOrigin`
field in the iframe config. The origin configuration of this sample page
was done at build time by the CloudFormation stack that created it.

102 changes: 88 additions & 14 deletions lex-web-ui/static/iframe/bot-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

// AWS SDK script dynamically added to the DOM
// https://github.com/aws/aws-sdk-js
sdkLink: 'https://sdk.amazonaws.com/js/aws-sdk-2.60.0.min.js',
sdkLink: 'https://sdk.amazonaws.com/js/aws-sdk-2.82.0.min.js',
};

/*
Expand All @@ -53,6 +53,7 @@
var iframe;
var container;
var messageHandler = {};
var credentials;

if (isSupported()) {
// initialize iframe once the DOM is loaded
Expand All @@ -78,7 +79,10 @@
}

function main() {
loadConfig(configUrl)
loadConfigFromJsonFile(configUrl)
.then(function loadConfigFromEventPromise(conf) {
return loadConfigFromEvent(conf);
})
.then(function assignConfig(conf) {
config = conf;
return Promise.resolve();
Expand Down Expand Up @@ -124,8 +128,8 @@
/**
* Loads the bot config from a JSON file URL
*/
function loadConfig(url) {
return new Promise(function loadConfigPromise(resolve, reject) {
function loadConfigFromJsonFile(url) {
return new Promise(function loadConfigFromJsonFilePromise(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
Expand All @@ -147,6 +151,75 @@
});
};

/**
* Loads dynamic bot config from an event
* Merges it with the config passed as parameter
*/
function loadConfigFromEvent(conf) {
return new Promise(function waitForConfigEvent(resolve, reject) {
var timeoutInMs = conf.configEventTimeOutInMs || 10000;

var timeoutId = setTimeout(onConfigEventTimeout, timeoutInMs);
document.addEventListener('loadlexconfig', onConfigEventLoaded, false);

var intervalId = setInterval(emitReceiveEvent, 500);
// signal that we are ready to receive the dynamic config
function emitReceiveEvent() {
var event = new Event('receivelexconfig');
document.dispatchEvent(event);
};

function onConfigEventLoaded(evt) {
clearTimeout(timeoutId);
clearInterval(intervalId);
document.removeEventListener('loadlexconfig', onConfigEventLoaded, false);

if (evt && ('detail' in evt) && evt.detail && ('config' in evt.detail)) {
var evtConfig = evt.detail.config;
var mergedConfig = mergeConfig(conf, evtConfig);
return resolve(mergedConfig);
} else {
return reject('malformed config event: ' + JSON.stringify(evt));
}
};

function onConfigEventTimeout() {
clearInterval(intervalId);
document.removeEventListener('loadlexconfig', onConfigEventLoaded, false);
return reject('config event timed out');
};
});

/**
* Merges config objects. The initial set of keys to merge are driven by
* the baseConfig. The srcConfig values override the baseConfig ones.
*/
function mergeConfig(baseConfig, srcConfig) {
// use the baseConfig first level keys as the base for merging
return Object.keys(baseConfig)
.map(function (key) {
var mergedConfig = {};
var value = baseConfig[key];
if (key in srcConfig) {
value = (typeof baseConfig[key] === 'object') ?
// recursively merge sub-objects in both directions
Object.assign(
mergeConfig(srcConfig[key], baseConfig[key]),
mergeConfig(baseConfig[key], srcConfig[key]),
) :
srcConfig[key];
}
mergedConfig[key] = value;
return mergedConfig;
})
.reduce(function (merged, configItem) {
return Object.assign({}, merged, configItem);
},
{}
);
};
}

/**
* Adds a div container to document body which will wrap the chat bot iframe
*/
Expand Down Expand Up @@ -202,12 +275,12 @@
return Promise.reject('unable to find AWS object');
}

AWS.config.region = config.aws.region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: config.aws.cognitoPoolId,
});
credentials = new AWS.CognitoIdentityCredentials(
{ IdentityPoolId: config.aws.cognitoPoolId },
{ region: config.aws.region },
);

return Promise.resolve();
return credentials.getPromise()
}

/**
Expand All @@ -220,21 +293,21 @@
console.log('[INFO] found existing identity ID: ', identityId);
}

if (!('getPromise' in AWS.config.credentials)) {
if (!('getPromise' in credentials)) {
console.error('getPromise not found in credentials');
return Promise.reject('getPromise not found in credentials');
}

return AWS.config.credentials.getPromise()
return credentials.getPromise()
.then(function storeIdentityId() {
console.log('[INFO] storing identity ID:',
AWS.config.credentials.identityId
credentials.identityId
);
localStorage.setItem('cognitoid', AWS.config.credentials.identityId);
localStorage.setItem('cognitoid', credentials.identityId);
identityId = localStorage.getItem('cognitoid');
})
.then(function getCredentialsPromise() {
return Promise.resolve(AWS.config.credentials);
return Promise.resolve(credentials);
});
}

Expand All @@ -261,6 +334,7 @@

function onIframeLoaded(evt) {
clearTimeout(timeoutId);
iframeElement.removeEventListener('load', onIframeLoaded, false);
toggleShowUi();
return resolve(iframeElement);
};
Expand Down
Loading

0 comments on commit 8979ff2

Please sign in to comment.