Changelog

New Features

⚠️ Note that each new feature is listed under all libraries it affects.

@interopio/browser

  • Added configuration for the allow attribute of the <iframe> element in which the app will be started when opened in a Workspace:
const definitions = [
    // App definition.
    {
        name: "my-app",
        type: "window",
        details: {
            url: "https://example.com",
            // Permissions policy for the `<iframe>` element in which the app will be started when opened in a Workspace.
            iframePermissionsPolicy: {
                // List of flags that will be applied to the `allow` attribute of the `<iframe>` element.
                flags: "geolocation 'self' https://a.example.com https://b.example.com; fullscreen 'none'"
            }
        }
    }
];

await io.appManager.inMemory.import(definitions);
  • Updated the methods of the io.Connect Channels API to be able to send and receive FDC3 context data in order to facilitate using FDC3 User Channel contexts in non-FDC3 interop-enabled apps.

The data object in a ChannelContext object may now contain an fdc3 property if FDC3 context data has been published to the Channel. This allows non-FDC3 interop-enabled apps to use FDC3 User Channel contexts more easily. The optional fdc3 property is an object with a required type property holding the type of the FDC3 context. This is valid for all methods of the Channels API that utilize the ChannelContext object in any way:

// Example of accessing FDC3 context data by using the `get()` method.
// The `ChannelContext` returned by `get()` will contain a `data.fdc3` property if FDC3 context data has been published to the Channel.
const context = await io.channels.get("Red");

if (context.data.fdc3) {
    // The `type` property of the `fdc3` object is required.
    const contextType = context.data.fdc3.type;

    console.log(contextType);
};

// Example of subscribing for FDC3 context data by using the `subscribe()` method.
// The `data` argument received by the callback will have an `fdc3` property if FDC3 context data has been published to the Channel.
const handler = (data) => {
    if (data.fdc3) {
        // The `type` property of the `fdc3` object is required.
        const contextType = data.fdc3.type;

        console.log(contextType);
    };
};

const unsubscribe = io.channels.subscribe(handler);

The publish() method of the Channels API now accepts also a PublishOptions object as a second optional argument. You can use it to specify the name of the Channel to update and whether the published data is FDC3 context data. This allows non-FDC3 interop-enabled apps to work with FDC3 contexts more easily:

// FDC3 context data to publish.
const data = {
    type: "fdc3.contact",
    name: "John Doe",
    id: {
        email: "john.doe@example.com"
    }
};

const options = {
    name: "Red",
    // Specify that the published data is FDC3 context data.
    fdc3: true
};

await io.channels.publish(data, options);

The get(), getMy(), subscribe(), and subscribeFor() methods for retrieving and subscribing to Channel contexts now accept an FDC3Options object as an optional argument. You can use this argument to specify the type of the FDC3 context in which you are interested. The get() and getMy() methods will resolve with an empty object if FDC3 context data of the specified type isn't available in the Channel. The callback passed to the subscribe() and subscribeFor() methods won't be invoked unless FDC3 context data of the specified type is published in the Channel:

// Specifying the type of an FDC3 context.
const fdc3Options = { contextType: "fdc3.contact" };

// Retrieving an FDC3 context of a specific type.
const fdc3Context = await io.channels.getMy(fdc3Options);

// Subscribing to an FDC3 context of a specific type.
const handler = (data) => {
    // The `type` property of the `fdc3` object is required.
    const contextType = data.fdc3.type;

    console.log(contextType);
};

const unsubscribe = io.channels.subscribe(handler, fdc3Options);
const intentRequest = {
    name: "ShowChart",
    // Clearing a previously saved handler for this Intent.
    clearSavedHandler: true
};

await io.intents.raise(intentRequest);

// Clearing all saved handlers for all previously raised Intents.
await io.intents.clearSavedHandlers();

@interopio/browser-platform

  • io.Insights now comes as a built-in feature of io.Connect Browser. io.Insights is disabled by default. To enable it and configure its features in io.Connect Browser, use the otel property of the configuration object when initializing your Main app:
import IOBrowserPlatform from "@interopio/browser-platform";

const config = {
    licenseKey: "my-license-key",
    // Enabling io.Insights and providing settings for publishing data.
    otel: {
        metrics: {
            url: "http://localhost:4242/my-metrics-collector",
            metrics: [
                {
                    type: "app_error",
                    enabled: false
                }
            ]
        }
    }
};

const { io } = await IOBrowserPlatform(config);
  • New configuration settings are available for adding extra headers to the requests sent to io.Manager, for caching and persisting data from io.Manager locally, and for the timeouts of the requests sent to io.Manager.

To add extra headers to the requests sent to io.Manager, use the getHeaders property of the manager object. Provide a callback that will receive as an argument all current headers and will be invoked on each request to io.Manager. The following example demonstrates how to provide a callback that will obtain and add to the headers a valid access token on each request sent to io.Manager:

import IOBrowserPlatform from "@interopio/browser-platform";

// Callback for handling the request headers.
const getHeaders = async () => {
    // Your custom internal logic for refreshing and retrieving the access token.
    const token = await getAccessToken();

    const extraHeaders = {
        Authorization: `Bearer ${token}`
    };

    // The returned headers will be merged with the existing ones.
    return extraHeaders;
};

const config = {
    licenseKey: "my-license-key",
    manager: {
        url: "https://my-io-manager.com:4242/api",
        // Providing a callback that will be invoked on every request sent to io.Manager.
        getHeaders
    }
};

const { io } = await IOBrowserPlatform(config);

To specify settings for caching data from io.Manager, use the cache property of the manager object. The cache object has the following properties:

Property Type Description
clearOld boolean If true, on opening a new session to io.Manager, all cache databases from previous sessions will be deleted. Defaults to false.
enabled boolean If true (default), will enable caching and persisting data from io.Manager locally (e.g., in case of connection interruptions).

To specify settings for the timeouts of the requests sent to io.Manager, use the requests property of the manager object. The requests object has the following properties:

Property Type Description
closeSessionTimeout number Interval in milliseconds to wait for a response to the closeSession request to io.Manager. Defaults to 10000.
openSessionTimeout number Interval in milliseconds to wait for a response to the openSession request to io.Manager before proceeding from cache. Defaults to 10000.
timeout number Interval in milliseconds to wait for a response from io.Manager. Defaults to 10000.
  • Added global configuration for the allow attribute of the <iframe> elements in Workspaces:
import IOBrowserPlatform from "@interopio/browser-platform";
import IOWorkspaces from "@interopio/workspaces-api";

const config = {
    licenseKey: "my-license-key",
    workspaces: {
        src: "https://my-workspaces-app.com",
        // Permissions policy for the `<iframe>` elements in a Workspace.
        iframePermissionsPolicy: {
            // List of flags that will be applied to the `allow` attribute of each `<iframe>` element in a Workspace.
            flags: "geolocation 'self' https://a.example.com https://b.example.com; fullscreen 'none'"
        }
    },
    browser: {
        libraries: [IOWorkspaces]
    }
};

const { io } = await IOBrowserPlatform(config);
  • Added support for saving preferred Intent handlers.
  • Exposed details about the initial caller of the Intents Resolver UI.

@interopio/fdc3

  • Updated the methods of the io.Connect Channels API to be able to send and receive FDC3 context data in order to facilitate using FDC3 User Channel contexts in non-FDC3 interop-enabled apps.

  • Added support for saving preferred Intent handlers.

@interopio/home-ui-react

  • Redesigned and improved the initial platform setup. The platform will now initialize even if the user doesn't grant the necessary browser permissions. Added steps for pop-ups permission and for installing the Home App as a Progressive Web Apps (PWA). If you have created your custom Home App by using the io.Connect CLI, it will be fully configured as a PWA. If you are using the @interopio/home-ui-react library directly, you will have to configure your custom Home App as a PWA if you want to use it as such.

Initial Setup

  • Added a <SystemLoader /> component to the library that can be used to customize the default loader of the Home App via the components property of the config object for the <IOConnectHome /> component:
import { IOConnectHome, SystemLoader } from "@interopio/home-ui-react";
import { getConfig } from "./helpers";
import "@interopio/workspaces-ui-react/dist/styles/workspaces.css";
import "@interopio/home-ui-react/src/index.css";

const getIOConnectConfig = await getConfig();

// Configuration for the `<IOConnectHome />` component.
const config = {
    getIOConnectConfig,
    components: {
        // Providing a custom icon for the default loader of the Home App.
        SystemLoader: () => <SystemLoader Logo={() => <span>My Logo</span>} />
    }
};

const App = () => {
    return <IOConnectHome config={config} />
};

export default App;
  • Added the option for integrating a custom login procedure when using the Home App. When using a custom login procedure, you have to display your own login page to the user, authenticate them, and then display the <IOConnectHome /> component and pass the user credentials to it:
import { useState } from "react";
import { IOConnectHome } from "@interopio/home-ui-react";
import { getConfig, authenticateUser, logOutUser } from "./helpers";
import MyLoginPage from "./MyLoginPage";
import "@interopio/workspaces-ui-react/dist/styles/workspaces.css";
import "@interopio/home-ui-react/src/index.css";

const App = () => {
    const [user, setUser] = useState(null);

    const config = {
        getIOConnectConfig: getConfig,
        // Settings for custom login.
        login: {
            // Type must be set to `"custom"`.
            type: "custom",
            // Provide details about the authenticated user.
            user: user: {
                id: user?.id,
                username: user?.username
            },
            // Callback that will be invoked on logout of the platform.
            onLogout: () => {
                // Use your custom logic to log out the user.
                logOutUser();

                setUser(null);
            }
        }
    };

    const logInUser = async () => {
        // Use your custom logic to authenticate the user.
        const user = await authenticateUser();

        if (user) {
            // Set the user object to provide an ID and a username for the Home App.
            setUser(user);
        };
    };

    // Display the Home App only if the user is logged in.
    return user ? <IOConnectHome config={config} /> : <MyLoginPage logInUser={logInUser} />
};

export default App;
  • The Launchpad now won't be displayed in the Home App if the Home App isn't a Browser Platform app.

@interopio/intents-resolver-api

  • Exposed information about the initial calling app of the Intents Resolver UI via the caller property of the Intents Resolver API. The caller property returns a ResolverCaller object holding the ID, name and title of the calling app:
const caller = io.intents.resolver.caller;
  • The callbacks passed to the onHandlerAdded() and onHandlerRemoved() methods now receive an IntentInfo object as a second optional argument which you can use to obtain details about the Intent that the app can handle.

  • The sendResponse() method now accepts a SendResolverResponseOptions object as a second optional argument. You can use this object to save the chosen Intent handler as a handler for the raised Intent. If the Intents Resolver UI was opened via the filterHandlers() method instead of the raise() method, you also have to specify the name of the Intent for which the handler will be saved:

const options = {
    saveHandler: true,
    // Providing the Intent name is necessary only when the Intents Resolver UI
    // was opened via the `filterHandlers()` method instead of the `raise()` method.
    name: "ShowChart"
};

await io.intents.resolver.sendResponse(selectedIntentHandler, options);

@interopio/intents-resolver-ui-react

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

npm install @interopio/intents-resolver-ui-react

Using the <IOConnectIntentsResolverUI /> component:

import IOBrowser from "@interopio/browser";
import IOConnectIntentsResolver from "@interopio/intents-resolver-api";
import IOConnectIntentsResolverUI from "@interopio/intents-resolver-ui-react";
// The default styles for the Intents Resolver App must be imported.
import "@interopio/intents-resolver-ui-react/styles";

// Provide the factory function for the `@interopio/intents-resolver-api` library.
const config = {
    libraries: [IOConnectIntentsResolver]
};

// Initialize the io.Connect API.
const io = await IOBrowser(config);

const domElement = document.getElementById("root");
const root = ReactDOM.createRoot(domElement);

root.render(
    // Provide the initialized io.Connect API object to the component.
    <IOConnectIntentsResolverUI config={{ io }} />
);

@interopio/widget

  • Added a displayInWorkspace property to the widget object for configuring the io.Connect widget. If set to true, the widget will be displayed in the app even if the app is currently inside a Workspace:
import IOBrowser from "@interopio/browser"
import IOBrowserWidget from "@interopio/widget";
import "@interopio/widget/styles";

const config = {
    libraries: [IOBrowserWidget],
    widget: {
        displayInWorkspace: true
    }
};

const io = await IOBrowser(config);

Improvements & Bug Fixes

⚠️ Note that each improvement or bug fix is listed under all libraries it affects.

@interopio/browser

  • Added error events and event handlers for platform and client errors.

@interopio/browser-platform

  • Added error events and event handlers for platform and client errors.
  • Fixed controller variable error that was causing a reference error.

@interopio/fdc3

  • Improved handling of dynamic registration of Intent handlers.
  • Changed log level to warn instead of error when broadcast() is used while not joined to a Channel.

@interopio/home-ui-react

  • Improved handling permission and user check when the window isn't a Browser Platform.
  • Improved instructions for the notifications permission prompt if it's automatically blocked by the browser.
  • Fixed useBeforeunload() firing on logout.

@interopio/intents-resolver-api

  • Optimized the type of the IOConnectIntentsResolver() factory function.
  • Added @interopio/browser as a dependency.

@interopio/widget

  • Added a check to prevent the widget from showing in an app if the app is a Workspaces App acting as a Main app.
  • Fixed a bug where Channels wouldn't be displayed when switching from default to compact mode.
  • Improved types.