This article walks through how to configure the SendSafely Dropzone Modal to work with the Amazon Connect. This example allows the end-user to upload files at any time using a button that is displayed at the bottom of the chat window.
Installing and Initializing the Modal
The Dropzone Modal javascript should be included on every page that runs the Amazon Connect widget where you want users to be able to upload files..
<script src="https://files.sendsafely.com/js/SendSafelyDropzoneModal.js" type="module"></script>
Next, we define some custom Javascript that is needed to post messages from our modal to the AWS Connect "Participant" web service and to show/hide the upload button when the widget is shown/hidden from view.
//These values are specific to your installation of AWS Connect
var amzSnippetId = "PUT_YOUR_AMZ_SNIPPET_ID_HERE";
var awsConnectStartUrl = "PUT_YOUR_AWS_CONNECT_START_URL_HERE";
var awsConnectParticipantEndpoint = "PUT_YOUR_CONNECT_PARTICIPANT_ENDPOINT_URL_HERE";
//This function posts messages to the AWS Connect Participat service
async function makeAwsConnectRequest(endpoint, headers, body) {
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`HTTP Error - Status: ${response.status}`);
}
const data = await response.json();
console.log('Success:', data);
return data;
} catch (error) {
throw new Error('Error:', error);
}
}
//This event listener obtains the user's persistedChatSession from AWS Connect
// and uses the modal's observeElementVisibility function to show/hide the upload button.
window.addEventListener('message', (event) => {
if (event.data.call === "amazon-connect-chat-started" && event.data.persistedChatSession)
{
console.log("Setting connectionToken");
persistedChatSession = event.data.persistedChatSession;
myDropzoneModal.observeElementVisibility(document.getElementById('amazon-connect-widget-frame'), (isVisible, element) => {
console.log(`Element is now ${isVisible ? 'visible' : 'hidden'}`);
if (isVisible)
{
//document.getElementById('custom-cta-btn').style.display = 'none';
myDropzoneModal.showUploadButton();
}
else
{
//document.getElementById('custom-cta-btn').style.display = 'flex';
myDropzoneModal.hideUploadButton();
}
});
}
});
The last step is to define a function to initialize the modal. This function includes onUploadComplete
callback logic to insert a success message that links to the files in SendSafely after the upload is complete. Note that we also pass some custom CSS properties to the uploadButtonStyle
property to visually match the upload button with the chat window.
// Initialize upload widget
let myDropzoneModal = null;
function createDropzoneModal() {
return new SendSafelyDropzoneModal({
dropzoneId: 'PUT_YOUR_SENDSAFELY_DROPZONE_ID_HERE',
url: 'PUT_YOUR_SENDSAFELY_HOSTNAME_HERE',
invokeDropzoneConnectors: false,
uploadButtonStyle: {
maxWidth: '301px',
height: '48px',
position: 'static',
background: 'rgb(18, 52, 86)',
boxShadow: 'rgba(0, 0, 0, 0.12) 0px 12px 48px 4px',
borderRadius: '0 0 16px 16px',
color: '#ffffff',
},
onUploadComplete: (url) => {
if (persistedChatSession)
{
//Exchange persistedChatSession token for participantToken
makeAwsConnectRequest(
awsConnectStartUrl,
{'x-amz-snippet-id':amzSnippetId},
{'persistedChatSession':persistedChatSession}
).then((response) => {
console.log(response);
var participantToken = response.data.startChatResult.ParticipantToken;
//Exchange participantToken for connectionToken
makeAwsConnectRequest(
awsConnectParticipantEndpoint + '/participant/connection',
{'X-Amz-Bearer':participantToken,'Content-type':'application/json'},
{'Type':['CONNECTION_CREDENTIALS']}
).then((response) => {
console.log(response);
var connectionToken = response.ConnectionCredentials.ConnectionToken;
//Use connectionToken to post message
makeAwsConnectRequest(
awsConnectParticipantEndpoint + '/participant/message',
{'X-Amz-Bearer':connectionToken,'Content-type':'application/json'},
{"Content": "[Upload Succeeded](" + url + ")","ContentType": "text/markdown"}
).then((response) => {
console.log(response);
//DONE
});
});
});
}
else
{
//This should generally never happen
alert("Please provide the following link to the agent: " + url);
}
},
onUploadError: (type, message) => {
//This should generally never happen
alert('SendSafely upload failed: ' + (message || type) + ". Please notify the agent.");
}
});
}
Using the Modal
Now that the appropriate code has been added to the page, you are ready to invoke and test the modal. As shown below, the button should appear automatically at the bottom of the chat window with the text “Attach File”. The text and icon shown on the button can be customized using the optional buttonText
and buttonIcon
configuration parameters when initializing the modal.
Pressing the button at any point during a conversation will trigger the modal to open.
After pressing submit, the modal will close and the link is rendered within the conversation using logic from the onUploadComplete callback function.
Complete Example Code
The full HTML from the example above is included below for reference. You will need to update the example to use a valid SendSafely Dropzone ID and modify the AWS Connect related variables for your specific AWS Connect instance.
<!DOCTYPE html>
<html>
<body>
<script src="https://files.sendsafely.com/js/SendSafelyDropzoneModal.js" type="module"></script>
<script type="text/javascript">
/*
Helpful Resources:
https://docs.aws.amazon.com/connect/latest/adminguide/supported-snippet-fields.html
https://github.com/amazon-connect/amazon-connect-chatjs
https://docs.aws.amazon.com/connect/latest/adminguide/pass-customization-object.html
https://github.com/amazon-connect/amazon-connect-chat-ui-examples
*/
var amzSnippetId = "PUT_YOUR_AMZ_SNIPPET_ID_HERE";
var awsConnectStartUrl = "PUT_YOUR_AWS_CONNECT_START_URL_HERE";
var awsConnectParticipantEndpoint = "PUT_YOUR_PARTICIPANT_ENDPOINT_HERE";
(function(w, d, x, id){
s=d.createElement('script');
s.src='https://db08fjupg2abb.cloudfront.net/amazon-connect-chat-interface-client.js';
s.async=1;
s.id=id;
d.getElementsByTagName('head')[0].appendChild(s);
w[x] = w[x] || function() { (w[x].ac = w[x].ac || []).push(arguments) };
})(window, document, 'amazon_connect', 'PUT_YOUR_AWS_CONNECT_INSTANCE_ID_HERE');
amazon_connect('styles', { iconType: 'CHAT', openChat: { color: '#ffffff', backgroundColor: '#123456' }, closeChat: { color: '#ffffff', backgroundColor: '#123456'} });
amazon_connect('snippetId', amzSnippetId);
amazon_connect('supportedMessagingContentTypes', [ 'text/plain', 'text/markdown', 'application/vnd.amazonaws.connect.message.interactive', 'application/vnd.amazonaws.connect.message.interactive.response' ]);
let myDropzoneModal = null;
function createDropzoneModal() {
return new SendSafelyDropzoneModal({
dropzoneId: 'PUT_YOUR_SENDSAFELY_DROPZONE_ID_HERE',
url: 'PUT_YOUR_SENDSAFELY_HOSTNAME_HERE',
invokeDropzoneConnectors: false,
uploadButtonStyle: {
maxWidth: '301px',
height: '48px',
position: 'static',
background: 'rgb(18, 52, 86)',
boxShadow: 'rgba(0, 0, 0, 0.12) 0px 12px 48px 4px',
borderRadius: '0 0 16px 16px',
color: '#ffffff',
},
onUploadComplete: (url) => {
if (persistedChatSession)
{
//Exchange persistedChatSession token for participantToken
makeAwsConnectRequest(
awsConnectStartUrl,
{'x-amz-snippet-id':amzSnippetId},
{'persistedChatSession':persistedChatSession}
).then((response) => {
console.log(response);
var participantToken = response.data.startChatResult.ParticipantToken;
//Exchange participantToken for connectionToken
makeAwsConnectRequest(
awsConnectParticipantEndpoint + '/participant/connection',
{'X-Amz-Bearer':participantToken,'Content-type':'application/json'},
{'Type':['CONNECTION_CREDENTIALS']}
).then((response) => {
console.log(response);
var connectionToken = response.ConnectionCredentials.ConnectionToken;
//Use connectionToken to post message
makeAwsConnectRequest(
awsConnectParticipantEndpoint + '/participant/message',
{'X-Amz-Bearer':connectionToken,'Content-type':'application/json'},
{"Content": "[Upload Succeeded](" + url + ")","ContentType": "text/markdown"}
).then((response) => {
console.log(response);
//DONE
});
});
});
}
else
{
//This should generally never happen
alert("Please provide the following link to the agent: " + url);
}
},
onUploadError: (type, message) => {
//This should generally never happen
alert('SendSafely upload failed: ' + (message || type) + ". Please notify the agent.");
}
});
}
async function makeAwsConnectRequest(endpoint, headers, body) {
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`HTTP Error - Status: ${response.status}`);
}
const data = await response.json();
console.log('Success:', data);
return data;
} catch (error) {
throw new Error('Error:', error);
}
}
window.addEventListener('message', (event) => {
if (event.data.call === "amazon-connect-chat-started" && event.data.persistedChatSession)
{
console.log("Setting connectionToken");
persistedChatSession = event.data.persistedChatSession;
const observer = observeElementVisibility(document.getElementById('amazon-connect-widget-frame'), (isVisible, element) => {
console.log(`Element is now ${isVisible ? 'visible' : 'hidden'}`);
if (isVisible)
{
//document.getElementById('custom-cta-btn').style.display = 'none';
myDropzoneModal.showUploadButton();
}
else
{
//document.getElementById('custom-cta-btn').style.display = 'flex';
myDropzoneModal.hideUploadButton();
}
});
}
});
//This is for monitoring the DOM for a specific element and tracking its visibility
function observeElementVisibility(element, callback) {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
console.log(mutation);
if (mutation.type === 'attributes' &&
(mutation.attributeName === 'style' || mutation.attributeName === 'class')) {
const isVisible = isElementVisible(element);
callback(isVisible, element);
}
});
});
observer.observe(element, {
attributes: true,
attributeFilter: ['style', 'class']
});
return observer; // Return so you can disconnect later
}
//Determine if an element is visible or not
function isElementVisible(element) {
const style = window.getComputedStyle(element);
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
style.opacity !== '0';
}
// Watch for new elements
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
console.log('New element added:', node);
if (node.id === "amazon-connect-chat-widget-iframe")
{
myDropzoneModal = createDropzoneModal();
myDropzoneModal.showUploadButton();
const button = document.querySelector('.sendsafely-upload-btn');
const target = document.querySelector('#amazon-connect-widget-frame');
target.after(button);
}
// Do something with the new element
if (node.tagName === 'IFRAME') {
console.log('New iframe detected!');
}
}
});
});
});
// Start watching
observer.observe(document.body, {
childList: true, // Watch for direct children changes
subtree: true // Watch all descendants too
});
</script>
</body>
</html>
Comments
0 comments
Please sign in to leave a comment.