More

Service Windows

Service windows are hidden windows which perform a specific supporting role for your apps. They can be defined as any normal window (name, URL, etc.), the difference being that UI configuration isn't necessary, as it is assumed that the purpose of these windows is to provide some background service to your apps. Therefore, the user doesn't need to see them or interact with them.

Service windows may be useful in many scenarios. For instance, you may have a number of apps that will receive and process data from several different providers. Instead of setting up each app to receive and then process the data from every provider, you can create a hidden service window which will communicate with the providers, collect the data, pre-process it and route it to the respective apps. This way, your apps will handle communication with only one end point, all the necessary data is consolidated, processed and filtered at one central data hub from where it can be sent to any window. Depending on your needs and goals, you can configure your service windows to auto start on system startup, or to start when an app requests that service. The service windows approach offers you additional flexibility and versatility in designing solutions for the app services you need.

Service windows can be defined as "window", "exe" or "node" type apps. There are different ways to configure a service window, depending on whether you want the window to be automatically started when io.Connect Desktop is initiated. Use a combination of the following app definition properties to specify whether the window should be automatically started, invisible, or hidden from the io.Connect launcher:

Property Type Description
"autoStart" boolean If true, the window will be started automatically on io.Connect Desktop startup.
"details" object Use the "hidden" Boolean property of the "details" object to set the window visibility. If true, the window will be invisible.
"hidden" boolean If true, the app won't be available in the io.Connect launcher.
"service" boolean If true, both the "autoStart" top-level key and the "hidden" property of the "details" object will be overridden and set to true. The window will be invisible and will start automatically on io.Connect Desktop startup.

⚠️ Note that when using the "service" property, it's pointless to use the "autoStart" top-level key and the "hidden" property of the "details" object, because the "service" key will override any values you may set for them.

The following example demonstrates how to use the "service" and "hidden" top-level keys to configure a service window that will start automatically, will be invisible and hidden from the io.Connect launcher:

{
    "name": "service-window",
    "type": "window",
    "service": true,
    "hidden": true,
    "details": {
        "url": "https://example.com/my-service-window",
    }
}

The following example demonstrates how to use the "hidden" and "autoStart" top-level keys and the "hidden" property of the "details" top-level key to configure a service window that will be hidden from the io.Connect launcher, won't start automatically and will be invisible:

{
    "name": "service-window",
    "type": "window",
    "hidden": true,
    "autoStart": false,
    "details": {
        "url": "https://example.com/my-service-window",
        "hidden": true
    }
}

⚠️ Note that service windows aren't closed when restoring a Layout.

For more details, see the Developers > Configuration > Application section and the app definition schema.

Citrix Apps

io.Connect Desktop provides experimental support for Citrix Virtual Apps. Citrix apps can participate in the io.Connect environment as first-class citizens - they can be configured and added to the io.Connect launcher, saved in Layouts and Workspaces, and can use all io.Connect functionalities like Interop, Channels, etc.

Additionally, io.Connect Desktop can be run as a Citrix Virtual App itself, in which case any other Virtual Apps from the same VDA can be defined as normal apps. See Dynamic Gateway Port for configuration specifics.

For more details on configuring a Citrix app, see the Developers > Configuration > Application section. For details on configuring the system-wide Citrix Virtual Apps support, see the System Configuration section.

⚠️ Note that this feature is experimental – although it has been properly tested, additional tests and adjustments might be necessary for your specific Citrix environment.

⚠️ Note that in order for io.Connect Desktop to run Citrix Virtual Apps, Citrix Workspace must be installed on the user's machine and the user must be logged into it using their Citrix StoreFront URL and credentials. If you have access to a web-based StoreFront, you can configure your local Citrix Workspace by clicking on the "Activate" link in the settings or user preferences menu and running the downloaded file. The StoreFront SSL certificate must be trusted by the user's machine.

.NET Citrix Apps

To interop-enable a .NET Citrix app:

  1. In your Visual Studio project, reference the Glue42.dll available in the NuGet package.

  2. Follow the standard procedure for interop-enabling .NET apps.

  3. After initializing io.Connect, you can check whether your app is connected to io.Connect in the following manner:

using Tick42.StartingContext;
if (InitializeOptions.IsCitrixGD)
{
    // Running in Citrix, connected to io.Connect Desktop.
}
else if (InitializeOptions.IsCitrixVirtualApp)
{
    // Running in Citrix, not connected to io.Connect Desktop.
}

When your Citrix app is connected to io.Connect Desktop, you may want to remove any custom window decorations, since the top-level window chrome will be handled by io.Connect Desktop.

  1. Add %** to the app arguments in the Citrix Application Settings:

Citrix Application Settings

  1. Define your app as an io.Connect Citrix app.

You will now be able to run your .NET Citrix app from io.Connect Desktop and interoperate with it using the various io.Connect APIs.

Java Citrix Apps

To interop-enable a Java Citrix app:

  1. Follow the standard procedure for interop-enabling Java apps.

  2. In the Citrix Application Settings, set the path to a javaw.exe or java.exe file, use standard VM arguments to launch your Java app, and add %** at the end of the app arguments:

Citrix Application Settings

  1. Define your app as an io.Connect Citrix app.

You will now be able to run your Java Citrix app from io.Connect Desktop and interoperate with it using the various io.Connect APIs.

Preload Scripts

The app definition file allows you to specify preload scripts for an app. The preload scripts will be executed before the actual web app is loaded and before each <iframe> on the page. Use the "preloadScripts" array of the "details" top-level key in the app definition file to define the scripts and they will be executed in the specified order. This allows for easily injecting io.Connect functionality into third-party web apps over which you have little to no control.

The following example demonstrates defining two preload scripts by providing their respective URLs:

{
    "details": {
        "preloadScripts": [
            "https://my-domain.com/my-script.js",
            "https://my-domain.com/my-other-script.js"
        ]
    }
}

Preload scripts can be defined globally in the system configuration of io.Connect Desktop. Use the "preloadScripts" property of the "windows" top-level key in the system.json file:

{
    "windows": {
        "preloadScripts": [
            "https://my-domain.com/my-script.js",
            "https://my-domain.com/my-other-script.js"
        ]
    }
}

The "preloadScripts" property also accepts an object with the following properties as a value:

Property Type Description
"scripts" string[] List of preload scripts that will be loaded and executed before the actual page is executed.
"useBase64PreloadScripts" boolean If true (default), will import the preload scripts as Base64 strings.

The "useBase64PreloadScripts" property allows you to control how io.Connect Desktop will import preload scripts. To improve this behavior, it's recommended to set the "useBase64PreloadScripts" property to false:

{
    "windows": {
        "preloadScripts": {
            "scripts": [
                "https://my-domain.com/my-script.js",
                "https://my-domain.com/my-other-script.js"
            ],
            "useBase64PreloadScripts": false
        }
    }
}

Available since io.Connect Desktop 9.1

The "scripts" property of the configuration object for the "preloadScripts" key now accepts also an array of objects which you can use to specify detailed settings for the preload scripts:

{
    "windows": {
        "preloadScripts": {
            "scripts": [
                {
                    "url": "https://my-domain.com/my-script.js",
                    "fallback": "https://my-domain.com/my-fallback-script.js",
                    "timeout": 5000
                },
                {
                    "url": "https://my-domain.com/my-other-script.js",
                    "fallback": "https://my-domain.com/my-other-fallback-script.js",
                    "timeout": 5000
                }
            ],
            "useBase64PreloadScripts": false
        }
    }
}

Each object in the "scripts" array has the following properties:

Property Type Description
"fallback" string URL pointing to a local or a remote preload script that will be used as a fallback in case the one specified in the "url" property doesn't load.
"timeout" number Interval in milliseconds to wait for the preload script to load.
"url" string Required. URL pointing to a local or a remote preload script.

Global Protocol Handler

io.Connect Desktop can be registered as a default global protocol handler. You can also register your own custom protocol handlers.

The io.Connect global protocol allows you to create and send links which will open a URL in an io.Connect Window. You can also create links that will start an interop-enabled app, load a specified Workspace or Layout and even invoke Interop methods with custom arguments.

When the link is clicked, io.Connect Desktop will be started in order to handle it. If an instance of io.Connect Desktop is already running, it will be reused. If multiple instances are running (e.g., in different environments), then the user will be presented with a dialog from which to choose the io.Connect instance that will handle the link:

Protocol Dialog

Configuration

The io.Connect global protocol can be configured from the system.json file of io.Connect Desktop using the "protocolHandler" top-level key:

{
    "protocolHandler": {
        "enabled": true,
        "allowOpeningURLs": {
            "allowed": ["https://interop.io"],
            "forbidden": ["https://youtube.com/.*", "https://facebook.com/.*"]
        }
    }
}

⚠️ Note that the "protocolHandler" key accepts either a single object, or an array of objects. Passing an array of objects allows you to define more than one custom protocol handlers.

The "protocolHandler" object has the following properties:

Property Type Description
"allowOpeningURLs" boolean | object If true (default), will allow handling all URLs. Can also be set to an object containing a list of allowed or forbidden URLs for the io.Connect global protocol handler. This setting won't be applied to any custom protocol handlers. If you want to use this setting in your custom protocol implementation, you can access its value via the object passed as an argument to your custom protocol handler.
"customHandler" string Interop method that will be used as a custom global protocol handler. Defaults to "T42.GD.ProtocolHandler.Handle".
"enabled" boolean If true (default), will enable registering the default io.Connect protocol handler, as well as any other custom protocol handlers.
"protocol" string Custom name for the protocol prefix. Defaults to "ioconnect".
"register" string If true (default), io.Connect Desktop will register the global protocol handler in the system registry. Set to false if you want to register the protocol some other way.
"startNewInstance" object If enabled, will start a new io.Connect instance whenever a command for the default io.Connect or any custom global protocol handler is received.
"type" "interopio" | "custom" Type of the global protocol handler. If "custom", then the Interop method defined in "customHandler" will be invoked to handle the protocol command. Otherwise, it will be handled by the default io.Connect protocol handler. Defaults to "interopio".

The "startNewInstance" object has the following properties:

Property Type Description
"defaultExecutable" object Object with two properties - "path" and "args". The "path" property accepts a string path to an executable file that will be started if no other io.Connect instance is running. Defaults to the io.Connect executable file. The "args" property accepts a string array with items, each item representing a command line argument. Defaults to an empty array. If the path points to a script file that in turn will launch io.Connect Desktop, then you must pass the startup arguments from the script to io.Connect Desktop.
"enabled" boolean If true (default), will allow starting a new instance of io.Connect when opening links with the io.Connect protocol handler.
"errorMessage" string Message that will override the default error message displayed when starting a new io.Connect instance is disabled.

The "allowOpeningURLs" property can be set to an object with the following properties:

Property Type Description
"allowed" string[] List of allowed URLs. Defaults to [].
"forbidden" string[] List of forbidden URLs. Defaults to [].

You can use exact URL values or regular expressions to specify allowed and forbidden URLs.

Protocol Options

The io.Connect global protocol can be used in different formats depending on what you want to do. The default io.Connect protocol is in the following format:

ioconnect://<option>/<identifier>[?args&args]

To pass arguments when employing the different options of the io.Connect global protocol, use a single ? after the identifier, except with url - use double ?? when passing arguments for the url protocol option. Use & between the arguments when specifying more than one argument.

Apps

To start an interop-enabled app, use the app protocol option and pass the app name:

ioconnect://app/clientlist

To pass startup options for an interop-enabled app, use ? after the app identifier and & before each settings. The following example demonstrates passing a location and context for the started app:

ioconnect://app/clientlist?left=100&context.clientID=1

To specify a property of an object as an option, use the standard dot notation - e.g., context.clientID=42.

Layouts

To restore a Global Layout, use the layout protocol option and pass the name of the Layout:

ioconnect://layout/StartOfDay

Workspaces

To open a Workspace, use the workspace protocol option and pass the Workspace name:

ioconnect://workspace/StartOfDay

To pass context for the Workspace, use context:

ioconnect://workspace/StartOfDay?context.clientID=1

To specify a property of an object as an option, use the standard dot notation - e.g., context.clientID=42.

io.Connect Windows

To open a URL in an io.Connect Window, use the url protocol option and pass the URL:

ioconnect://url/https://google.com 

To specify io.Connect Window settings when opening a URL, use ?? after the URL and & before each setting. The following example demonstrates passing a location for the newly opened window:

ioconnect://url/https://google.com??left=100&top=200

To specify a property of an object as a setting, use the standard dot notation - e.g., downloadSettings.autoSave=false.

Interop Methods

To invoke an Interop method, use the invoke protocol option and pass the method name:

ioconnect://invoke/Shutdown

To pass arguments and/or target when invoking an Interop method, use args and target:

ioconnect://invoke/ShowClient?args.clientId=1&target=best

Custom Protocol Handlers

Besides the default io.Connect global protocol handler, you can define your own custom protocol handlers. You must specify the name of the protocol prefix and the name of the Interop method that will be used as a protocol handler. The specified Interop method will be invoked each time a command for your custom protocol is received. The Interop method that will handle your custom protocol may be registered by an auto started hidden service app or any other way you deem fit.

Configuration

The following example demonstrates defining two custom protocol handlers, one of which will be added to the system registry by io.Connect Desktop and the other won't:

{
    "protocolHandler": [
        {
            "enabled": true,
            "protocol": "my-custom-protocol",
            "type": "custom",
            "customHandler": "My.Custom.Protocol.Handler"
        },
        {
            "enabled": true,
            // This protocol won't be registered by the platform.
            // You have to add it to the system registry some other way.
            "register": false,
            "protocol": "my-other-custom-protocol",
            "type": "custom",
            "customHandler": "My.Other.Custom.Protocol.Handler"
        }
    ]
}

For more details on the configuration options, see the Global Protocol Handler > Configuration section.

Implementation

When io.Connect Desktop receives a protocol command, each available protocol handler will be invoked consecutively until the command has been handled or there are no more handlers. The handlers are invoked one after another and each one is awaited to finish processing the command before the next one is invoked.

If your protocol handler implementation returns a result or an object with a handled property set to true, the command will be considered handled and no more handlers will be invoked. If your implementation doesn't return anything or returns an object with a handled property set to false, then the next available protocol handler will be invoked.

The following example demonstrates a basic implementation of a protocol handler:

// Name of the protocol handler as defined in the configuration.
const name = "My.Custom.Protocol.Handler";
// Protocol handler implementation.
const handler = (data) => {
    console.log(data);

    // If nothing or `{ handled: false }` is returned,
    // the next available protocol handler will be invoked.
    return { handled: true };
};

await io.interop.register(name, handler);

The object received as an argument by the protocol handler has the following properties:

Property Type Description
command string The executed protocol command.
config object The protocol handler configuration object as defined in the system configuration of io.Connect Desktop. This information may be useful if, for instance, you want to extract the protocol name or implement custom logic for handling the "allowOpeningURLs" property.

Remote Archived Apps

io.Connect Desktop can download, unarchive and run archived web or native apps hosted at a remote location. To specify the location of your archived app and headers for the request, use the "assets" top-level key in the app definition file.

The "assets" object has the following properties:

Property Type Description
"headers" object[] Array of objects, each with required "name" and "value" properties, defining the headers that will be added to the request for downloading the archived app.
"noCache" boolean If true, the Cache-Control: no-cache header will be added.
"src" string URL pointing to the location of the archived app that will be downloaded and extracted.

The following example demonstrates how to define the location of a remote archived web app, specify headers for the request, include the Cache-Control: no-cache header, and define the app entry point:

{
    "name": "my-archived-web-app",
    "type": "window",
    "asset": {
        "src": "https://example.com/my-archived-web-app.zip",
        "headers": [
            {
                "name": "header-name",
                "value": "header-value"
            }
        ],
        "noCache": true
    },
    "details": {
        "url": "$ASSET_LOCATION$/home.html"
    }
}

⚠️ Note that if you don't specify an entry point for your web app in the "url" property, io.Connect Desktop will try to access it via an index.html file.

The following example demonstrates how to define the location of a remote archived native app:

{
    "name": "my-archived-native-app",
    "type": "exe",
    "asset": {
        "src": "http://example.com/my-archived-native-app.zip"
    },
    "details": {
        "command": "MyArchivedNativeApp.exe",
        "path": "$ASSET_LOCATION$"
    }
}

The default location for downloading remote apps is %LocalAppData%/interop.io/io.Connect Desktop/UserData/<ENV>-<REG>/remoteApps/<app-name>, where <ENV>-<REG> represents the environment and region of io.Connect Desktop (e.g., DEMO-INTEROP.IO) and <app-name> is the name of the app as defined in its configuration. You can use the $ASSET_LOCATION$ macro pointing to the default location of the extracted app when defining the "path" or the "url" property of your app, depending on its type.

For details on specifying locations for the system folders of io.Connect Desktop, see the Developers > Configuration > System section.

Client Certificates

The users in an organization may be required to use various certificates for authentication when accessing different URLs. When a certificate is required, io.Connect Desktop opens a dialog that shows the available certificates for the user to choose:

Certificate Page

Users can check the "Always use the selected certificate if possible" checkbox to instruct io.Connect Desktop to attempt to use the selected certificate for any further requests. In case the selected certificate can't be applied to a future request, the dialog will open again. You can configure the visibility of this checkbox and whether it should be checked by default. Use the "clientCertificates" property of the "windows" top-level key in the system.json system configuration file of io.Connect Desktop:

{
    "windows": {
        "clientCertificates": {
            "alwaysUseCheckbox": {
                "enabled": true,
                "checked": true
            }
        }
    }
}

Users can reset the default certificates through the "Advanced" menu item in the tray menu of io.Connect Desktop:

Certificate Tray

If the "Reset Default Client Certificates" menu item is greyed out, this means that no certificates have been set as default yet.

The following specifics for resetting certificates should be taken into account:

  • Upon restart of io.Connect Desktop, the selected default certificates are automatically reset.
  • When the user resets the default certificates from the tray menu:
    • If a certificate has already been used successfully for a request, Chromium will keep using it despite the certificate having been reset in io.Connect Desktop and the dialog for selecting a certificate won't open for subsequent requests for the same resource. Only a restart of io.Connect Desktop will truly reset the certificate and trigger the prompt page.
    • If a certificate has been rejected by the server when attempting a request for a resource, resetting the default certificates from the tray menu will result in the dialog for selecting a certificate appearing again when the user makes a request for that same resource.

When for some reason a certificate doesn't pass the internal Chromium validation before being sent to the server, a dialog appears that offers options to the user to block or allow this certificate. The allowed certificates are then remembered for future use:

Certificate Error

The "Reset SSL Certificates Error Settings" option of the "Advanced" menu item of tray menu can be used to reset the already remembered allowed certificates, which will result in the dialog appearing again when a previously allowed invalid certificate is used.

Issue Reporting

io.Connect Desktop has a built-in Feedback Form that allows users to send feedback with improvement suggestions or bug reports. To report a problem or submit a suggestion, describe it in the "Description" field and optionally attach logs and configs to the report. The form can be configured to send an email with the report to the io.Connect team and/or to automatically create a Jira ticket with the issue reported by the user. Both on-premise and cloud-based Jira solutions are supported. The Feedback Form is a web app and can be completely customized.

Feedback Form

Feedback Button

The Feedback Form can be opened directly from an app instance by using the "Feedback" button in the app window header.

Feedback Button

The "Feedback" button is disabled by default and can be enabled globally from the system configuration of io.Connect Desktop or per app from the app definition file. The app definition will override the global system configuration.

Enable the "Feedback" button globally for all apps from the system.json configuration file using the "windows" top-level key:

{
    "windows": {
        "showFeedbackButton": true
    }
}

Disable the "Feedback" button for an app from its definition:

{
    "details": {
        "showFeedbackButton": false
    }
}

Use the "supportEmails" top-level key in the app definition to specify the emails of the app owners. The email addresses defined in this property will be added to the Feedback Form if it has been triggered from that app:

{
    "supportEmails": ["app.owner1@example.com", "app.owner2@example.com"]
}

Configuration

To configure the Feedback Form, use the "issueReporting" top-level key in the system.json file of io.Connect Desktop located in %LocalAppData%/interop.io/io.Connect Desktop/Desktop/config. The following is an example Feedback Form configuration:

{
    "issueReporting": {
        "attachmentsViewMode": "category",
        "jira": {
            "enabled": true,
            "url": "https://jira.my-org.com/rpc/soap/jirasoapservice-v2",
            "user": "user",
            "password": "password",
            "project": "MY_PROJECT"
        },
        "mail": {
            "enabled": true,
            "zipAttachments": true,
            "bugSubjectPrefix": "Error:",
            "reportSubjectPrefix": "Feedback:",
            "recipients": ["support@example.com", "dev-support@example.com"],
            "allowEditRecipients": true,
            "maxAttachmentMB": "10"
        },
        "folderAttachments": [
            {
                "name": "Screenshots",
                "folderPath": "%GLUE-USER-DATA%/logs",
                "zipFolderPath": "io.Connect Desktop/Screenshots",
                "filter": "*.png",
                "category": "Screenshots",
                "selected": false,
                "recursive": true
            },
            {
                "name": "io.Connect Desktop-AppLogs",
                "folderPath": "%GLUE-USER-DATA%/logs",
                "zipFolderPath": "io.Connect Desktop/Logs",
                "filter": "*.log*",
                "category": "App Logs",
                "selected": true,
                "recursive": true
            },
            {
                "name": "io.Connect Desktop-Crashes",
                "folderPath": "%GLUE-USER-DATA%/crashes/reports",
                "zipFolderPath": "io.Connect Desktop/Crashes",
                "filter": "*.dmp",
                "category": "Crashes",
                "recursive": false
            }
        ],
        "form": {
            "width": 350,
            "height": 500
        }
    }
}

The "issueReporting" top-level key has the following properties:

Property Type Description
"attachmentsViewMode" "file" | "category" Defines how the attachments will be displayed in the Feedback Form. Possible values are "file" or "category". If set to "file", all attachable files will be displayed as separate items, otherwise several main categories will be displayed and if the category is selected by the user, all files in it will be attached.
"attachScreenShots" boolean If true, will attach screenshots of all monitors to the issue report.
"folderAttachments" object[] Required. Attachments configuration. For more details, see the Attachments section.
"form" object Object that can be used to specify various properties for the io.Connect Window containing the Feedback Form, or to provide a URL pointing to a custom Feedback app.
"jira" object Required. Jira configuration. For more details, see the Jira section.
"mail" object Required. Email configuration. For more details, see the Email section.

Jira

The configuration for automatically creating a Jira ticket when submitting a Feedback Form is found under the required "jira" property of the "issueReporting" top-level key. It accepts an object with the following properties:

Property Type Description
"enabled" boolean If true, will enable the option to create a Jira ticket when submitting the issue.
"noEnvironment" boolean If true, the ticket "Environment" field won't be set.
"noPriority" boolean If true, the ticket "Priority" field won't be set.
"password" string Required. The password of the user who will create the ticket.
"preferredRole" string Preferred role in the Jira project.
"preferredRoleDescriptor" string Description of the preferred role in the Jira project.
"project" string Required. The name of the project in which the ticket will be created.
"tlsVersion" string Force TLS protocol version, e.g. "TLS1.2".
"useProjectLeadAsPreferredAssignee" boolean If true, will assign the ticket to the project lead.
"user" string Required. The username of the user who will create the ticket.
"url" string Required. Link to the Jira API.

Mail

The configuration for automatically sending an email when submitting a Feedback Form is found under the required "mail" property of the "issueReporting" top-level key. It accepts an object with the following properties:

Property Type Description
"allowEditRecipients" boolean If true, the user will be allowed to manually add more recipients.
"bugSubjectPrefix" string Prefix for the email subject when the issue is a bug.
"enabled" boolean If true, will enable the option to send an email when submitting the issue.
"maxAttachmentMB" string The maximum size of the attachments in MB.
"recipients" string[] List of email addresses to which the issue report will be sent.
"reportSubjectPrefix" string Prefix for the email subject when sending feedback.
"zipAttachments" boolean If true, the attachments will be archived.

Attachments

The configuration for attaching files when submitting a Feedback Form is found under the required "folderAttachments" property of the "issueReporting" top-level key. It accepts an array of objects, each with the following properties:

Property Type Description
"category" string Required. Category for the attached item.
"filter" string Required. Regex filter that will be applied to each file in the folder - e.g., "*.log".
"folderPath" string Required. Path to the folder that will be attached.
"name" string Required. Name for the attachment.
"recursive" boolean If true, will collect and attach all files from all folders located inside the specified folder. Set to false to collect the files only in the specified folder.
"selected" boolean If true, the category (or all files under this category) in the Feedback Form will be selected by default.
"zipFolderPath" string Required. Path in the archived folder.

Form

To configure the io.Connect Window containing the Feedback Form, use the "form" property of the "issueReporting" top-level key. It accepts an object with the following properties:

Property Type Description
"height" number Height in pixels for the Feedback Form.
"showEnvironmentInfo" boolean If true, the Feedback Form will show a field with details about the io.Connect environment.
"transparent" boolean If true, the io.Connect Window containing the Feedback Form will have a transparent background. Available since io.Connect Desktop 9.1.
"url" string URL pointing to the location of the Feedback app. Defaults to "file://%GDDIR%/assets/issue-reporting/index.html".
"width" number Width in pixels for the Feedback Form.

⚠️ Note that the "transparent" property will set a transparent background only for the io.Connect Window that contains the Feedback app. To achieve actual transparency, you must also set a transparent background for your app from its styles.

Extending the Feedback Form

The @interopio/components-react library enables you to create your own Feedback app for io.Connect Desktop. The library provides hooks and default components which you can use to build your own Feedback app by using, modifying or replacing the available components and functionalities.

To use the library in your project, execute the following command:

npm install @interopio/components-react

⚠️ Note that the @interopio/components-react library doesn't include a built Feedback app. A Feedback app is provided in io.Connect Desktop. You can also use and customize the Feedback app template.

Configuration

To use your custom Feedback app built with the @interopio/components-react library, modify (or add) the "form" property to the "issueReporting" top-level key in the system.json file of io.Connect Desktop located in %LocalAppData%/interop.io/io.Connect Desktop/Desktop/config. Set the "url" property of the "form" object to the URL of your custom Feedback app:

{
    "issueReporting": {
        "form": {
            "url": "http://localhost:3000"
        }
    }
}

The "form" object has the following properties:

Property Type Description
"height" number Height in pixels for the Feedback Form.
"showEnvironmentInfo" boolean If true, the Feedback Form will show a field with details about the io.Connect environment.
"url" string URL pointing to the location of the Feedback app. Defaults to "file://%GDDIR%/assets/issue-reporting/index.html".
"width" number Width in pixels for the Feedback Form.

For more available customizations of the Feedback app through configuration, see the Issue Reporting > Configuration section.

Hooks

The useIssueReportingContext() hook provides context information about the Feedback app, as well as methods for the default Feedback functionalities which you can use directly in your custom components:

const {
    close,
    config,
    onThemeChanged,
    openUrl,
    setBounds,
    submit,
    submitCompleted,
} = useIssueReportingContext();
Property Type Description
close() function Closes the Feedback app.
config object Object describing the configuration for the Feedback Form.
onThemeChanged() (callback: (theme: string) => void) => void Method that can be used to track theme changes. The callback parameter is invoked every time the theme changes and receives as an argument the name of the new theme.
openUrl() (url: string) => void Opens a URL in the default browser. Can be used to open the URL of the newly created Jira ticket after the issue report has been submitted.
setBounds() (left: number, top: number, width: number, height: number) => void Sets the bounds of the Feedback Form.
submit() function Submits an issue report.
submitCompleted boolean Boolean flag indicating whether the issue report submit process has been completed.

The config object has the following properties:

Property Type Description
allowEditRecipients boolean Flag indicating whether the user is allowed to manually add or remove email recipients.
applicationTitle string Title of the Feedback app.
attachments object[] Array of objects each with id, name and category properties describing the available attachments.
attachmentsViewMode "file" | "category" Describes how the attachments are displayed in the Feedback app.
buildVersion string Platform build version as specified in the "build" top-level key in the system.json file of io.Connect Desktop.
createJiraTicket boolean Flag indicating whether a Jira ticket will be created when the issue report has been submitted.
env string Platform environment as specified in the "env" top-level key in the system.json configuration file of io.Connect Desktop.
environmentInfo string Platform environment details.
errorMessage string Error message sent by the platform.
isError boolean Flag indicating whether the submitted feedback is about a platform error.
mailingList string List of default email recipients.
region string Platform region as specified in the "region" top-level key in the system.json configuration file of io.Connect Desktop.
selectedCategories string[] List of selected attachments or attachment categories.
sendEmail boolean Flag indicating whether an email with the issue report will be sent to the specified recipients.
showEnvironmentInfo boolean Flag indicating whether the Feedback app will show a field with environment details about the platform.
theme string The name of the current theme.
version string Platform version.

Components

All default components can be reused and composed with custom code. If usage of such component has been detected, its default behavior will be applied. For instance, if you use the <SubmitButton /> component, it will automatically submit the issue report when the button is clicked, without the need of custom code to induce this behavior. If you pass the same component more than once, an error will be thrown.

To remove a component, pass a <Fragment /> component. If you want to use a default functionality (e.g., for closing the Feedback Form) in your custom components, use the useIssueReportingContext() hook to acquire it and pass it to your component.

In your custom Feedback app, you should place the <Feedback /> components in the <IssueReportingProvider /> component. This component is a React context provider component that provides feedback-related data and functionality accessible through the useIssueReportingContext() hook.

The following example demonstrates a basic usage of the library:

import React from "react";
import { IssueReportingProvider } from "@interopio/components-react";
import CustomFeedback from "./CustomFeedback";

const App = () => {
    return (
        <IssueReportingProvider>
            <CustomFeedback />
        </IssueReportingProvider>
    );
};

export default App;

Feedback Component

The <Feedback /> component is used for rendering the Feedback Form. The following example demonstrates the structure of the <Feedback /> component, its properties and default values:

<Feedback
    components={{
        Header: () => {
            return (
                <div>
                    <Title />
                    <HeaderButtons />
                </div>
            );
        },
        HeaderButtons: () => {
            return (
                <div>
                    <ButtonGroup>
                        <CloseButton />
                    </ButtonGroup>
                </div>
            );
        },
        Body: () => {
            return (
                <div>
                    <form></form>
                </div>
            );
        },
        Footer: () => {
            return (
                <div>
                    <FooterButtons />
                </div>
            );
        },
        FooterButtons: ({ submitCompleted }) => {
            if (submitCompleted) {
                return (
                    <ButtonGroup >
                        <ButtonWithIcon />
                    </ButtonGroup>
                );
            } else {
                return (
                    <ButtonGroup>
                        <CancelButton />
                        <SubmitButton />
                    </ButtonGroup>
                );
            };
        },
        SubmitButton: SubmitButton,
        CancelButton: CancelButton,
        CloseButton: CloseButton,
        Button: Button,
        ButtonGroup: ButtonGroup,
        ButtonWithIcon: ButtonWithIcon,
        Alert: Alert,
        Block: Block,
        Checkbox: Checkbox,
        Input: Input,
        Label: Label,
        Progress: Progress,
        Radio: Radio,
        Select: Select,
        Textarea: Textarea,
        Title: Title
    }}
/>

Header Component

The <Header /> component holds the <Title /> and the <CloseButton /> button components. The following example demonstrates creating a header for the Feedback app with a custom title and an additional custom header button. The default <CloseButton /> component is used with its default functionality for closing the Feedback app:

import React from "react";
import {
    Title,
    CloseButton,
    ButtonGroup,
    useIssueReportingContext
} from "@interopio/components-react";
import CustomButton from "./CustomButton";

// Custom header title.
const CustomTitle = () => {
    const { config } = useIssueReportingContext();
    const { applicationTitle } = config;
    const title = applicationTitle ? `My ${applicationTitle}` : "My Custom Feedback app";

    return <Title className="draggable" text={title} />;
};

// This component holds the header buttons.
const CustomHeaderButtons = () => {
    // Get the deafult funactionality for closing the Feedback app.
    const { close } = useIssueReportingContext();

    return (
        <ButtonGroup>
            <CustomButton />
            <CloseButton onClick={close} />
        </ButtonGroup>
    );
};

// Custom header for the Feedback app.
const CustomHeader = () => {
    return (
        <div>
            <CustomTitle />
            <CustomHeaderButtons />
        </div>
    );
};

export default CustomHeader;

To use the custom header for the Feedback app, pass it to the <Feedback /> component:

import React from "react";
import { Feedback, IssueReportingProvider } from "@interopio/components-react";
import CustomHeader from "./CustomHeader";

const App = () => {
    return (
        <IssueReportingProvider>
            <Feedback
                components={{
                    Header: CustomHeader
                }}
            />
        </IssueReportingProvider>
    );
};

export default App;

Custom Header

Body Component

You can use the <Body /> component to create a body for the Feedback app containing the desired issue reporting options - e.g., issue description field, attachments, Jira ticket and email options. The @interopio/components-react library provides various components to choose from when constructing the view of your custom form: <TextArea />, <Checkbox />, <Block />, <Input />, <Select /> and more (see Feedback Component).

The following example demonstrates creating a body for the Feedback app containing custom text informing the user what will happen when they click the "Submit" button, a text area for describing the issue, and a "Settings" section containing only a single restricted option for creating a Jira ticket:

import React from "react";
import {
    Block,
    Textarea,
    Checkbox,
    useIssueReportingContext
} from "@interopio/components-react";

// Custom body for the Feedback app.
const CustomBody = ({ handleSubmit, submitting }) => {
    const { config } = useIssueReportingContext();
    const { buildVersion, createJiraTicket } = config;
    const customText = `Your feedback will be submitted to the ${buildVersion} team`
        + (createJiraTicket ? " and a Jira ticket will be created." : ".")

    // The body contains the custom text, a text area for describing the issue, and a "Settings" section.
    return (
        <div>
            <form onSubmit={event => handleSubmit(event)}>
                <Block text={customText} />

                <Block text="Description">
                    <Textarea disabled={submitting} />
                </Block>

                {createJiraTicket &&
                <Block text="Settings">
                    <Checkbox
                        disabled={true}
                        defaultChecked={createJiraTicket}
                        label="Create Jira Ticket"
                    />
                </Block>}
            </form>
        </div>
    );
};

export default CustomBody;

To use the custom body for the Feedback app, pass it to the <Feedback /> component:

import React from "react";
import { Feedback, IssueReportingProvider } from "@interopio/components-react";
import CustomHeader from "./CustomFooter";
import CustomBody from "./CustomFooter";

const App = () => {
    return (
        <IssueReportingProvider>
            <Feedback
                components={{
                    Header: CustomHeader,
                    Body: CustomBody
                }}
            />
        </IssueReportingProvider>
    );
};

export default App;

Custom Body

By default, the <Footer /> component holds the <FooterButtons /> component that renders conditionally either the <SubmitButton /> and the <CancelButton /> components, or a "Close" button based on whether submitting the issue report has been completed. During submission, the <Footer /> component renders the <Progress /> component showing the submission progress. When the submission is completed, the <Footer /> component renders an <Alert /> component showing the status of the submission, and if creating a Jira ticket has been enabled in the system configuration, a URL pointing to the created Jira ticket is also shown to the user.

The following example demonstrates adding a custom footer button:

import React from "react";
import {
    SubmitButton,
    CancelButton,
    ButtonGroup,
    useIssueReportingContext
} from "@interopio/components-react";
import CallSupportButton from "./CallSupportButton";

// This component holds the footer buttons.
const CustomFooterButtons = () => {
    const { close } = useIssueReportingContext();

    return (
        <ButtonGroup>
            <SubmitButton form="feedback" type="submit" />
            <CallSupportButton />
            <CancelButton onClick={close} />
        </ButtonGroup>
    );
};

// Custom footer for the Feedback app.
const CustomFooter = () => {
    return (
        <div>
            <CustomFooterButtons />
        </div>
    );
};

export default CustomFooter;

To use the custom footer for the Feedback app, pass it to the <Feedback /> component:

import React from "react";
import { Feedback, IssueReportingProvider } from "@interopio/components-react";
import CustomHeader from "./CustomFooter";
import CustomBody from "./CustomFooter";
import CustomFooter from "./CustomFooter";

const App = () => {
    return (
        <IssueReportingProvider>
            <Feedback
                components={{
                    Header: CustomHeader,
                    Body: CustomBody,
                    Footer: CustomFooter
                }}
            />
        </IssueReportingProvider>
    );
};

export default App;

Custom Footer

Open App

The Open app of io.Connect Desktop is accessible from the tray menu. It's designed for users to be able to quickly and easily open io.Connect Windows from the UI by providing only a URL.

Advanced users can use the JSON editor in the "Advanced Options" section to provide any valid options for opening a window.

The following example demonstrates how to open a flat io.Connect Window, add the Channel Selector to it, and join the window to the "Red" Channel:

{
    "mode": "flat",
    "channelSelector": {
        "enabled": true,
        "channelId": "Red"
    }
}

Open Window

About App

The default About app of io.Connect Desktop is accessible from the tray menu. It shows details about io.Connect Desktop and its components, also license and any available meta information:

About App

To customize the About app of io.Connect Desktop, use the "about" top-level key in the system.json system configuration file.

The "about" object has the following properties:

Property Type Description
"height" number Height in pixels for the About app.
"hideTrademark" boolean Hide the trademark information section in the About app.
"url" string URL pointing to the location of the About app.
"width" number Width in pixels for the About app.

To remove the trademark information at the bottom of the page, use the "hideTrademark" property:

{
    "about": {
        "hideTrademark": true
    }
}

Hide Trademark

You can also change the entire content displayed in the About app and its size by providing a URL to be loaded instead of the default app and specifying the desired width and height.

The following example demonstrates how to modify the size and the content of the About app:

{
    "about": {
        "url": "https://interop.io/",
        "width": 500,
        "height": 500
    }
}

Custom About

Opening URLs in the Default Browser

io.Connect Desktop comes with a predefined app which can be used to open a URL in the default browser using the App Management API. The following example shows how to open a URL in the default browser by using the JavaScript App Management API.

Get the Application instance by passing the name of the app - "open-browser", invoke the start method to start the app and pass a starting context with a url property holding the URL:

const url = "https://interop.io";

await io.appManager.application("open-browser").start({ url });

⚠️ Note that only URLs with HTTP or HTTPS protocols can be opened.

Search in web apps opened in io.Connect Windows just like in a browser with the CTRL + F command:

Search

Use ENTER and SHIFT + ENTER to scroll through the results. Click ESC to close the search bar.

Context Menu

io.Connect Desktop has a right-click context menu available in all io.Connect apps for which it has been enabled. It offers standard cut/copy/paste actions, zoom and spelling controls:

Context Menu

Enable the context menu:

  • globally for all apps, under the "windows" top-level key in the system.json file:
{
    "windows": {
        "contextMenu": {
            "enabled": true
        }
    }
}
  • per app, under the "details" top-level key in the app definition file:
[
    {
        "details": {
            "contextMenuEnabled": true
        }
    }
]

Telemetry Data

io.Connect Desktop exposes telemetry data containing info related to the system environment and the running web apps.

On a system level, you can gather data about:

  • Focus duration, start and stop events for apps.
  • Step-by-step system startup flow events. Can be used to investigate slow startups.
  • All system log entries.
  • System CPU and memory usage.

For web apps, you can gather data about:

  • All network request per app.
  • All app console logs.
  • Web page exceptions caught by the window "error" event.
  • CPU and memory usage per process.

Telemetry data events can also be cleared. To get or clear telemetry data, you must invoke the "T42.Telemetry" Interop method passing the respective command and configuration for getting or clearing telemetry data entries.

Configuration

Telemetry data is disabled by default. To enable it, set the "telemetry" top-level key in the system.json configuration file of io.Connect Desktop to true:

{
    "telemetry": {
        "enabled": true
    }
}

Getting Data

To get telemetry data, invoke the "T42.Telemetry" Interop method. As a second argument for the invocation, pass an object with command and config properties. For the command property, use "GetEvents" as a value to specify that the method should get telemetry data. Use the config property to specify what type of telemetry data you want to receive - for the system environment, for the running web apps, or for both:

// Name of the Interop method.
const methodName = "T42.Telemetry";
// Settings for the method invocation.
const args = {
    command: "GetEvents",
    config: { system: true, apps: true }
};
// Invoking the "T42.Telemetry" method to get telemetry data.
const result = await io.interop.invoke(methodName, args);
// Extracting the returned data object.
const data = result.returned;

console.log(data);

The object passed as an argument for the method invocation has the following properties:

Property Type Description
command "GetEvents" | "ClearEvents" Specifies the action to execute - get or clear telemetry data.
config object Specifies what type of telemetry data to get or clear.

The config object for requesting telemetry data has the following properties:

Property Type Description
apps boolean | object Specifies whether to get web app data. Pass an object to refine the data request (see Refining Telemetry Requests).
system boolean | object Specifies whether to get system data. Pass an object to refine the data request (see Refining Telemetry Requests).

Data Shape

The object returned from the invocation of the "T42.Telemetry" Interop method when requesting telemetry data, has the following shape:

const data = {
    type: "gd",
    data: {
        system: {},
        apps: [],
        stores : {}
    }
};

The system object has the following properties:

Property Type Description
journeyEvents object[] Each item of the array holds information about focus duration, start and stop events for a specific web app.
logErrors object[] Each item of the array holds an error entry from the system logs.
perfData object Holds information about overall and per app CPU and memory usage.
startupEvents object[] Each item of the array holds information about a single startup flow event.

The apps property is an array of objects with the following properties:

Property Type Description
app string The app name as defined in its configuration file.
console object[] Each item of the array holds information about a message logged to the console.
errors object[] Each item of the array holds information about a web page exception caught by the window "error" event.
info object Contains specific data about the app - instance ID, API version, window type, URL and more.
instance string Unique ID within the io.Connect environment.
requests object[] Each item of the array holds information about a specific network request made by the app.

The stores object has the following properties:

Property Type Description
console object Contains information about the number of items in the store, the total store size and the time of the last update.
journeyEvents object Contains information about the number of items in the store, the total store size and the time of the last update.
logs object Contains information about the number of items in the store, the total store size and the time of the last update.
requests object Contains information about the number of items in the store, the total store size and the time of the last update.

Refining Telemetry Requests

When requesting telemetry data, you can specify what type of system and web app data entries you want to receive. Use the system and apps properties of the config object refine your telemetry request:

const methodName = "T42.Telemetry";
const args = {
    command: "GetEvents",
    config: {
        // Omit data about startup flow events and CPU and memory usage.
        system: { withStartupEvents: false, withPerfData: false },
        // Omit data about console messages logged by web apps.
        apps: { withConsole: false }
    }
};
const result = await io.interop.invoke(methodName, args);
const data = result.returned;

console.log(data);

The system property of the config object accepts an object with the following properties:

Property Type Description
withJourneyEvents boolean Whether to receive data about focus duration, start and stop events for web apps.
withLog boolean Whether to receive data about system log entries.
withPerfData boolean Whether to receive data about system CPU and memory usage.
withStartupEvents boolean Whether to receive data about the system startup flow events.

The apps property of the config object accepts an object with the following properties:

Property Type Description
withConsole boolean Whether to receive data about console messages logged by web apps.
withErrors boolean Whether to receive data about web page exceptions caught by the window "error" event.
withRequests boolean Whether to receive data about network requests made by web apps.

Clearing Data

To clear telemetry data, invoke the "T42.Telemetry" Interop method and pass "ClearEvents" as a value to the command property of the argument object for the method invocation. Use the config object to specify what type of telemetry data entries to clear:

// Name of the Interop method.
const methodName = "T42.Telemetry";
// Settings for the method invocation.
const args = {
    command: "ClearEvents",
    config: {
        requests: true,
        journeyEvents: true,
        logs: true,
        console: true
    };
};
// Invoking the "T42.Telemetry" method to clear telemetry data.
const result = await io.interop.invoke(methodName, args);
// Extracting the returned data object.
const data = result.returned;

// Will print an empty object, as all entries have been cleared.
console.log(data);

The config object for clearing telemetry data has the following properties:

Property Type Description
console boolean Whether to clear data about console messages logged by web apps.
journeyEvents boolean Whether to clear data about focus duration, start and stop events for web apps.
logs boolean Whether to clear data about system log entries.
requests boolean Whether to clear data about network requests made by web apps.

Adding DevTools Extensions

You can extend the Chrome DevTools in io.Connect Desktop with additional extensions. To add a DevTools Extension supported by Electron, you need to have the extension installed and add a configuration for it in the system.json file of io.Connect Desktop and in the configuration file of your app. The example below demonstrates adding the React DevTools Extension to io.Connect Desktop:

  1. Install the React DevTools Chrome extension.

  2. Locate the extension on your local machine - the default location for the React DevTools Extension is %LocalAppData%/Google/Chrome/User Data/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi. (You can move the extension installation folder wherever you like.)

  3. Open the system.json configuration file of io.Connect Desktop located in %LocalAppData%/interop.io/io.Connect Desktop/Desktop/config and add the path to the React DevTools Extension under the "devToolsExtensions" top-level array:

{
    "devToolsExtensions": [
        "C:/Users/<username>/AppData/Local/Google/Chrome/User Data/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/4.23.0_0"
    ]
}

Replace <username> with your local username. The path must point to the version folder of the extension containing the manifest.json file. Remember to escape the backslash characters.

  1. Open the JSON configuration file of your app and add the following configuration under the "details" top-level key:
{
    "security": {
        "sandbox": false
    }
}

For instance:

{
    "name": "My App",
    "details": {
        "url": "http://localhost:3000",
        "security": {
            "sandbox": false
        }
    }
}
  1. Start io.Connect Desktop, open your interop-enabled app and you should be able to see the added extension to the Chrome DevTools when you open the developer console. In this case, you will need a React app in order to be able to see the React DevTools Extension.