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.
Components
You can have several screens in the config wizard. On each screen you could have:
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:
1export const openConfigWindow = () => {2// Must open window from user interaction code otherwise it is likely3// to be blocked by a popup blocker:4const configWindow = window.open(undefined, '_blank', 'width=500,height=500,scrollbars=no');56// Listen to popup messages7let configFinished = false;8const onmessage = e => {9if (e.data.type === 'tray.configPopup.error') {10// Handle popup error message11alert(`Error: ${e.data.err}`);12}13if (e.data.type === 'tray.configPopup.cancel') {14configWindow.close();15}16if (e.data.type === 'tray.configPopup.finish') {17// Handle popup finish message18configFinished = true;19configWindow.close();20}21if (e.data.type === 'tray.configPopup.validate') {22// Return validation in progress23configWindow.postMessage(24{25type: 'tray.configPopup.client.validation',26data: {27inProgress: true,28},29},30'*'31);3233setTimeout(() => {34// Add errors to all inputs35const errors = e.data.data.visibleSlots.reduce((errors, externalId) => {36console.log(`Visible ${externalId} value:`, e.data.data.slotValues[externalId]);37// Uncomment next line to set an error message38// errors[externalId] = 'Custom error message';39return errors;40}, {});4142// Return validation43configWindow.postMessage(44{45type: 'tray.configPopup.client.validation',46data: {47inProgress: false,48errors: errors,49},50},51'*'52);53}, 2000);54}55};56window.addEventListener('message', onmessage);5758// Check if popup window has been closed before finishing the configuration.59// We use a polling function due to the fact that some browsers may not60// display prompts created in the beforeunload event handler.61const CHECK_TIMEOUT = 1000;62const checkWindow = () => {63if (configWindow.closed) {64// Handle popup closing65if (!configFinished) {66alert('Configuration not finished');67} else {68alert(69'Configuration finished. You can enable the new ' +70'solution instance from the "Solutions > My Instances" section'71);72console.log('Configuration finished');73}74window.removeEventListener('message', onmessage);75} else {76setTimeout(checkWindow, CHECK_TIMEOUT);77}78};7980checkWindow();8182return configWindow;83};
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.
1openPopup = (openInIframe, addCustomValidation = false) => {2updateSolutionInstanceConfig(this.props.id).then(({ body }) => {3const url = addCustomValidation4? `${body.data.popupUrl}&customValidation=true`5: body.data.popupUrl;67if (!openInIframe) {8const configWindow = openConfigWindow();9configWindow.location = url;10} else {11this.setState({12configWizardSrc: url,13});14}15});16};
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:
1componentDidMount() {2window.addEventListener("message", this.handleIframeEvents);3}45componentWillUnmount() {6window.removeEventListener("message", this.handleIframeEvents);7}89handleIframeEvents = (e) => {10console.log(`${e.data.type} event received`);11// Here we should handle all event types12if (e.data.type === 'tray.configPopup.error') {13alert(`Error: ${e.data.err}`);14}15if (e.data.type === 'tray.configPopup.cancel') {16this.props.onClose();17}18if (e.data.type === 'tray.configPopup.ready') {19this.setState({ ready: true });20}21if (e.data.type === 'tray.configPopup.finish') {22this.props.onClose();23}24};
Connected topics
Read next
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: