Tray Embedded / Building Integrations / The Config Wizard

The Config Wizard

Read first

Make sure you have read the following sections before proceeding:

Introduction

The Tray Solution Configuration Wizard pop-up is activated at the following url:

https://embedded.tray.io/external/solutions/${embeddedId}/configure/${solutionInstanceId}?code=${authorizationCode}

In the above url remember that ${embeddedId} is the value that you set when configuring your Config Wizard CSS

You can remove the tray.io domain and whitelabel the Config Wizard url. If you have done so then embedded.tray.io will be replaced with e.g. acme.integration-configuration.com

Note on Browser Compatibility: The Tray Config Wizard supports the major desktop and mobile browsers Chrome, Firefox, Safari (9.1+ desktop and 9.3+ iOS), Opera, Android (and Android UC) and Edge (version 14 and above). It does not support Internet Explorer.

Subscribing to events from the Tray Configuration Wizard from your application

The Configuration Wizard will publish some events by PostMessage for your application to potentially handle.

tray.configPopup.finish - Will be sent with no payload if there has been a successful configuration of the Solution Instance and the user has reached the finish page.

tray.configPopup.error - Will be sent with an err property in the case an error occurs during configuration.

tray.configPopup.cancel - Will be sent with no payload if the user cancels the configuration.

tray.configPopup.validate - Used to indicate that you should start the validation process (see the next page on validation in this menu section).

You can subscribe to these events from your application with a window.addEventListener

The following code from our demo app (from src/lib/configWindow.js) shows a way of handling these events:

export const openConfigWindow = () => {
// Must open window from user interaction code otherwise it is likely
// to be blocked by a popup blocker:
const configWindow = window.open(undefined, '_blank', 'width=500,height=500,scrollbars=no');
// Listen to popup messages
let configFinished = false;
const onmessage = e => {
if (e.data.type === 'tray.configPopup.error') {
// Handle popup error message
alert(`Error: ${e.data.err}`);
}
if (e.data.type === 'tray.configPopup.cancel') {
configWindow.close();
}
if (e.data.type === 'tray.configPopup.finish') {
// Handle popup finish message
configFinished = true;
configWindow.close();
}
if (e.data.type === 'tray.configPopup.validate') {
// Return validation in progress
configWindow.postMessage(
{
type: 'tray.configPopup.client.validation',
data: {
inProgress: true,
},
},
'*'
);
setTimeout(() => {
// Add errors to all inputs
const errors = e.data.data.visibleSlots.reduce((errors, externalId) => {
console.log(`Visible ${externalId} value:`, e.data.data.slotValues[externalId]);
// Uncomment next line to set an error message
// errors[externalId] = 'Custom error message';
return errors;
}, {});
// Return validation
configWindow.postMessage(
{
type: 'tray.configPopup.client.validation',
data: {
inProgress: false,
errors: errors,
},
},
'*'
);
}, 2000);
}
};
window.addEventListener('message', onmessage);
// Check if popup window has been closed before finishing the configuration.
// We use a polling function due to the fact that some browsers may not
// display prompts created in the beforeunload event handler.
const CHECK_TIMEOUT = 1000;
const checkWindow = () => {
if (configWindow.closed) {
// Handle popup closing
if (!configFinished) {
alert('Configuration not finished');
} else {
alert(
'Configuration finished. You can enable the new ' +
'solution instance from the "Solutions > My Instances" section'
);
console.log('Configuration finished');
}
window.removeEventListener('message', onmessage);
} else {
setTimeout(checkWindow, CHECK_TIMEOUT);
}
};
checkWindow();
return configWindow;
};

Dealing with browser security

It is important that you deal with the possibility of a pop-up being blocked by the browser. Generally, browsers will not block pop-ups if they are invoked by direct user action (i.e. a button click). Some browsers will be able to tell if a sequence of calls was started by a button-click but it is advisable to keep the button-click as 'shallow' as possible. It is also good practice to test for pop-ups being blocked and take action such as asking the user 'please allow pop-ups for this site' (some discussions on this can be found here and here )

Other options

Setting the first screen in the Config Wizard

It is possible to specify which screen appears first in the Configuration Wizard by appending a query onto the url, such as:

https://embedded.tray.io/external/solutions/${embeddedId}/configure/${solutionInstanceId}?code=${authorizationCode}&show=[1,2,3,4]&start=2

You can remove the tray.io domain and whitelabel the Config Wizard url. If you have done so then embedded.tray.io will be replaced with e.g. acme.integration-configuration.com

Skipping Auth naming and settings

You can skip the title field of the authentication (it will be automatically named for you) by adding the skipTitleField=true query parameter to the above url.

For OAuth services (i.e. non-token-based) you can also skip the auth settings (i.e. login details) and go straight to the scopes page by using the skipAuthSettings=true query parameter.

For example:

https://embedded.tray.io/external/solutions/${embeddedId}/configure/${solutionInstanceId}?code=${authorizationCode}&skipTitleField=true&skipAuthSettings=true

Hiding the Authentication Button

If you wish to reduce the number of clicks an End User has to make it is possible to hide the 'New Authentication' button in the Config Wizard so that they go straight to the service authentication dialog.

This is done in the UI. When you are setting up the Configuration Wizard screens, it is possible to use the 'Skip CTA' option for any authentications:

Using an iframe

If you wish to present the Config Wizard in an iframe, your domain will need to be added to our whitelist. Please contact us at support@tray.io to arrange this.

Your domain will then need to be passed in the Referrer Request Header.

Apps must be deployed with https in order to use an iframe as we block requests from http sites for security reasons

Note that checks are done at the subdomain level, so 123.example.com will not be accepted if we have example.com in the whitelist.

Our demo app shows an example of using an iframe to present the Config Wizard.

Note that for local testing purposes, localhost:3000 and localhost:3001 are whitelisted for using iframes.

Don't forget that it is possible to specify which configuration screen appears first in the Configuration Wizard, and to skip the auth naming and settings page, as explained in Using a Standard pop-up

The src/components/ConfigWizard.js file sets up the post message event listeners for the iframe.

While src/components/Instance.js sets up the openPopup method which handles opening an iframe.

openPopup = (openInIframe, addCustomValidation = false) => {
updateSolutionInstanceConfig(this.props.id).then(({ body }) => {
const url = addCustomValidation
? `${body.data.popupUrl}&customValidation=true`
: body.data.popupUrl;
if (!openInIframe) {
const configWindow = openConfigWindow();
configWindow.location = url;
} else {
this.setState({
configWizardSrc: url,
});
}
});
};

Presenting a custom loading screen

During transition between stages in the Config Wizard you may find that the End User is presented with a combination of the Tray.io loading spinner and your own branded loading spinner.

When using an iFrame, you can control this more precisely and specify exactly what is presented, by making use of the tray.configPopup.ready PostMessage event.

You can program your app so that while that event has not occurred, your custom loading screen / spinner is displayed.

In our demo app (from src/components/configWizard.js) you can see this event being handled amongst the others described above for the standard pop-up:

componentDidMount() {
window.addEventListener("message", this.handleIframeEvents);
}
componentWillUnmount() {
window.removeEventListener("message", this.handleIframeEvents);
}
handleIframeEvents = (e) => {
console.log(`${e.data.type} event received`);
// Here we should handle all event types
if (e.data.type === 'tray.configPopup.error') {
alert(`Error: ${e.data.err}`);
}
if (e.data.type === 'tray.configPopup.cancel') {
this.props.onClose();
}
if (e.data.type === 'tray.configPopup.ready') {
this.setState({ ready: true });
}
if (e.data.type === 'tray.configPopup.finish') {
this.props.onClose();
}
};

Connected topics

The next page in this section will explain Solution Instances which is triggered when an End User clicks to configure a Solution for their own use: