# io.Connect Desktop 9.6

Source: https://docs.interop.io/desktop/getting-started/changelog/platform/9-6/index.html

## io.Connect Desktop 9.6

*Release date: 11.11.2024*

| Components | Version |
|------------|---------|
| Electron | [32.2.1](https://releases.electronjs.org/release/v32.2.1) |
| Chromium | 128.0.6613.186 |
| Node.js | 20.18.0 |

The following libraries are bundled with **io.Connect Desktop** 9.6 and will be used for [auto injection](https://docs.interop.io/desktop/getting-started/how-to/interop-enable-your-apps/javascript/index.md#auto_injection):

| Injected Library | Version |
|------------------|---------|
| [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) | [6.7](https://docs.interop.io/desktop/getting-started/changelog/libraries/interopio-desktop/index.md#67-670) |
| [`@interopio/fdc3`](https://www.npmjs.com/package/@interopio/fdc3) | 2.3 |

## Breaking Changes

> ### Channel Restrictions
>
> The `"preventModifyingRestrictionsFor"` property of the `"channelSelector"` object under the `"details"` top-level key in the [app definition](https://docs.interop.io/desktop/developers/configuration/application/index.md) now accepts an array of objects instead of an array of strings as a value. Each object in the array now allows you to specify the name of the Channel whose restrictions the user or the app won't be able to modify, as well as the type of the restriction - read or write.
>
> The following example demonstrates how to prevent the user and the app from modifying the read and write restrictions for the specified Channel:
>
> ```json
> {
>     "details": {
>         "channelSelector": {
>             "preventModifyingRestrictionsFor": [
>                 {
>                     "name": "Red",
>                     "read": false,
>                     "write": false
>                 }
>             ]
>         }
>     }
> }
> ```
>
> Each object in the `"preventModifyingRestrictionsFor"` array has the following properties:
>
> | Property | Type | Description |
> |----------|------|-------------|
> | `"name"` | `string` | Name of the Channel. |
> | `"read"` | `boolean` | If `true`, the users and the app will be able to modify the Channel restriction for subscribing for data (manually from the Channel Selector and programmatically via the Channels API). |
> | `"write"` | `boolean` | If `true`, the users and the app will be able to modify the Channel restriction for publishing data (manually from the Channel Selector and programmatically via the Channels API). |
## New Features

> ### Interception API
>
> Added an [Interception API](https://docs.interop.io/desktop/capabilities/interception/javascript/index.md) that enables your to register interception handlers from your apps in order to intercept low-level platform control messages for executing operations in the io.Connect API domains. The interception handlers can modify these messages in order to decorate the default operation implementations, prevent them, or replace them with your custom ones. The interception handlers can be invoked both before and after the execution of the default platform implementations of the intercepted operations.
>
> To enable your apps to register interception handlers, use the `"interception"` top-level key in the `system.json` [system configuration](https://docs.interop.io/desktop/developers/configuration/system/index.md) file of **io.Connect Desktop**.
>
> The following example demonstrates how to enable interception for all apps:
>
> ```json
> {
>     "interception": {
>         "enabled": true
>     }
> }
> ```
>
> The following example demonstrates how to register an interception handler for the `"raise"` operation of the Intents API domain (invoked by the [`raise()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-raise) method of the Intents API). The handler will be invoked during both interception phases - before and after the default platform implementation of the intercepted operation has been executed. In the `"before"` phase, the context data in the [`IntentRequest`](https://docs.interop.io/desktop/reference/javascript/intents/intentrequest/index.md) object with which the Intent was originally raised will be modified. In the `"after"` phase, the result in the [`IntentResult`](https://docs.interop.io/desktop/reference/javascript/intents/intentresult/index.md) object returned from executing the default platform operation will be modified:
>
> ```javascript
> // Specify which API domains and which operations within them to intercept.
> const interceptions = [
>     {
>         domain: "intents",
>         operation: "raise"
>         // If an interception phase isn't explicitly specified here, the handler
>         // will be invoked during both interception phases - before and after the default
>         // platform implementation of the intercepted operation has been executed.
>     }
> ];
>
> // Handler that will be invoked when the targeted platform operation has been intercepted.
> // The handler will receive as an argument an `InterceptionMessage` object describing the intercepted operation.
> const handler = (message) => {
>     const phase = message.phase;
>
>     // Defining the behavior of the handler based on the current interception phase.
>
>     if (phase === "before") {
>         // Extracting the `IntentRequest` object with which the Intent was originally raised.
>         let raiseIntentArgs = message.operationArgs[0];
>
>         // Modifying the context data with which to raise the Intent.
>         raiseIntentArgs.context.data = { io: 42 };
>
>         // The modified operation arguments must be returned as an `InterceptionResponse` object with an `operationArgs` property.
>         const modifiedArgs = { operationArgs: [raiseIntentArgs] };
>
>         // The execution of the default platform implementation will proceed with the modified arguments.
>         return modifiedArgs;
>     }
>
>     if (phase === "after") {
>         // Extracting the `IntentResult` object returned from executing
>         // the default platform implementation for raising an Intent.
>         let raiseIntentResult = message.operationArgs[0];
>
>         // Modifying the `IntentResult` object.
>         raiseIntentResult.context.data = { io: 42 };
>
>         // The modified operation result must be returned as an `InterceptionResponse` object with an `operationResult` property.
>         const modifiedResult = { operationResult: raiseIntentResult };
>
>         return modifiedResult;
>     }
> };
>
> // Configuration for registering an interception handler.
> const config = { interceptions, handler };
>
> // Registering an interception handler.
> await io.interception.register(config);
> ```

> ### Grouping Notification Actions
>
> You can now create [groups of notification actions](https://docs.interop.io/desktop/capabilities/notifications/javascript/index.md#notification_actions-grouping_notification_actions). This enables you to assign multiple actions to a single notification button. Nested actions are displayed as a dropdown menu:
>
> ![Grouping Notification Actions](https://docs.interop.io/desktop/images/notifications/grouping-actions.mp4)
>
> To group notification actions, use a combination of the `displayId` and `displayPath` properties of the [`NotificationAction`](https://docs.interop.io/desktop/reference/javascript/notifications/notificationaction/index.md) object when defining an action. The `displayId` property must be set to a unique ID for the action. This ID can be used as a value in the `displayPath` property of another notification action to determine the position of the latter within the notification action menu.
>
> The following example demonstrates how to create a notification with a single action button that will have two nested actions as sub-items:
>
> ```javascript
> const options = {
>     title: "New Trade",
>     body: "VOD.L: 23 shares sold @ $212.03",
>     actions: [
>         {
>             action: "edit",
>             title: "Edit",
>             onClick: () => { },
>             // Unique ID for the action.
>             displayId: "1",
>         },
>         {
>             action: "copy",
>             title: "Copy",
>             onClick: () => { },
>             // Using the ID of another action to specify the position
>             // of this action within the notification action menu.
>             displayPath: ["1"],
>         },
>         {
>             action: "paste",
>             title: "Paste",
>             onClick: () => { },
>             // Using the ID of another action to specify the position
>             // of this action within the notification action menu.
>             displayPath: ["1"],
>         }
>     ]
> };
>
> const notification = await io.notifications.raise(options);
> ```

> ### Notifications Bulk Edit
>
> It's now possible to edit notifications in bulk in the Notification Panel. Click the "Bulk Edit" button, select the notifications you want to edit, choose the action (snooze, mark as read or unread, delete), and click the "Done" button:
>
> ![Bulk Edit Notifications](https://docs.interop.io/desktop/images/notifications/bulk-edit.mp4)

> ### Notifications API
>
> #### Import
>
> Importing notifications can be useful if you want to persist the notification history between user sessions. Even if the user shuts down the platform, you can dynamically import the previously saved batch of existing notifications when the user logs in again. All imported notifications will be visible in the Notification Panel and will be available in the notification list that can be accessed programmatically, but only notifications with state set to `"Active"` will be displayed as notification toasts to the user. An event for a raised notification will be triggered for all imported notifications.
>
> To import a notification, use the [`import()`](https://docs.interop.io/desktop/reference/javascript/notifications/api/index.md#API-import) method and provide a list of [`IOConnectNotificationOptions`](https://docs.interop.io/desktop/reference/javascript/notifications/ioconnectnotificationoptions/index.md) objects as a required argument:
>
> ```javascript
> const notifications = [
>     {
>         title: "New Trade",
>         body: "VOD.L: 23 shares sold @ $212.03",
>         state: "Active"
>         actions: [
>             {
>                 action: "openClientPortfolio",
>                 title: "Open Portfolio"
>             }
>         ]
>     },
>     {
>         title: "New Trade",
>         body: "VOD.L: 42 shares bought @ $211.73",
>         state: "Seen"
>     }
> ];
>
> await io.notifications.import(notifications);
> ```
>
> #### Dismiss
>
> By default, when the user clicks on a notification in the Notification Panel, the notification will close. To disable this behavior and allow clicked notifications to remain in the Notification Panel, set the `"closeNotificationOnClick"` property of the `"notifications"` top-level key in the `system.json` [system configuration](https://docs.interop.io/desktop/developers/configuration/system/index.md) file of **io.Connect Desktop** to `false`:
>
> ```json
> {
>     "notifications": {
>         "closeNotificationOnClick": false
>     }
> }
> ```
>
> You can also configure this behavior programmatically by using the [`configure()`](https://docs.interop.io/desktop/reference/javascript/notifications/api/index.md#API-configure) method:
>
> ```javascript
> const settings = { closeNotificationOnClick: false };
>
> await io.notifications.configure(settings);
> ```

> ### Intents API
>
> To get notified when Intent handlers are added or removed, use the [`onHandlerAdded()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-onHandlerAdded) and [`onHandlerRemoved()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-onHandlerRemoved) methods respectively on top level of the Intents API. Provide a callback function for handling the event:
>
> ```javascript
> const handler = (intentHandler) => {
>     console.log(`App "${intentHandler.applicationName}" was registered as an Intent handler.`);
> };
>
> const unsubscribe = io.intents.onHandlerAdded(handler);
> ```
>
> To [clear](https://docs.interop.io/desktop/capabilities/data-sharing/intents/javascript/index.md#clearing_saved_intent_handlers) all previously [saved Intent handlers](https://docs.interop.io/desktop/capabilities/data-sharing/intents/overview/index.md#extending_the_intent_resolver-intent_resolver_api-saving_intent_handlers), use the [`clearSavedHandlers()`](https://docs.interop.io/desktop/reference/javascript/intents/api/index.md#API-clearSavedHandlers) method of the Intents API. To remove a saved handler for a specific Intent, use the `clearSavedHandler` property of the [`IntentRequest`](https://docs.interop.io/desktop/reference/javascript/intents/intentrequest/index.md) object when raising the Intent:
>
> ```javascript
> const intentRequest = {
>     name: "ViewChart",
>     // 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();
> ```

> ### Intent Resolver API
>
> Exposed information about the initial calling app of the Intent Resolver UI via the `caller` property of the Intent Resolver API. The `caller` property returns a [`ResolverCaller`](https://docs.interop.io/desktop/reference/javascript/intents/resolvercaller/index.md) object holding the ID, name and title of the calling app:
>
> ```javascript
> const caller = io.intents.resolver.caller;
> ```
>
> The callbacks passed to the [`onHandlerAdded()`](https://docs.interop.io/desktop/reference/javascript/intents/resolver/index.md#Resolver-onHandlerAdded) and [`onHandlerRemoved()`](https://docs.interop.io/desktop/reference/javascript/intents/resolver/index.md#Resolver-onHandlerRemoved) methods now receive an [`IntentInfo`](https://docs.interop.io/desktop/reference/javascript/intents/intentinfo/index.md) object as a second optional argument which you can use to obtain details about the Intent that the app can handle.
>
> The [`sendResponse()`](https://docs.interop.io/desktop/reference/javascript/intents/resolver/index.md#Resolver-sendResponse) method now accepts a [`SendResolverResponseOptions`](https://docs.interop.io/desktop/reference/javascript/intents/sendresolverresponseoptions/index.md) object as a second optional argument. You can use this object to [save the chosen Intent handler](https://docs.interop.io/desktop/capabilities/data-sharing/intents/overview/index.md#extending_the_intent_resolver-intent_resolver_api-saving_intent_handlers) as a handler for the raised Intent. If the Intent 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:
>
> ```javascript
> const options = {
>     saveHandler: true,
>     // Providing the Intent name is necessary only when the Intent Resolver UI
>     // was opened via the `filterHandlers()` method instead of the `raise()` method.
>     name: "ViewChart"
> };
>
> await io.intents.resolver.sendResponse(selectedIntentHandler, options);
> ```

> ### Intent Resolver React Component
>
> Added the [`@interopio/intents-resolver-ui-react`](https://www.npmjs.com/package/@interopio/intents-resolver-ui-react) library which exports the `<IOConnectIntentsResolverUI />` React component that can be used to [create an Intent Resolver App](https://docs.interop.io/desktop/capabilities/data-sharing/intents/overview/index.md#extending_the_intent_resolver-intent_resolver_component).
>
> To use the library in your project, execute the following command:
>
> ```cmd
> npm install @interopio/intents-resolver-ui-react
> ```
>
> Using the `<IOConnectIntentsResolverUI />` component:
>
> ```javascript
> import { createRoot } from "react-dom/client";
> import IODesktop from "@interopio/desktop";
> import IOConnectIntentsResolver from "@interopio/intents-resolver-api";
> import IOConnectIntentsResolverUI from "@interopio/intents-resolver-ui-react";
> // The default styles for the Intent 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],
>     // This is necessary for listening for app and instance events.
>     appManager: "full"
> };
>
> // Initialize the io.Connect API.
> const io = await IODesktop(config);
>
> const domElement = document.getElementById("root");
> const root = createRoot(domElement);
>
> root.render(
>     // Provide the initialized io.Connect API object to the component.
>     <IOConnectIntentsResolverUI config={{ io }} />
> );
> ```

> ### Spellchecker
>
> **io.Connect Desktop** now utilizes the built-in [Electron spellchecker](https://www.electronjs.org/docs/latest/tutorial/spellchecker) which is enabled by default. To provide settings for the spellchecker, use the `"spellchecker"` top-level key in the `system.json` [system configuration](https://docs.interop.io/desktop/developers/configuration/system/index.md) file of **io.Connect Desktop**:
>
> ```json
> {
>     "spellchecker": {
>         "enabled": true,
>         "languages": ["en-US", "fr"],
>         "dictionaryDownloadURL": "https://example.com/dictionaries"
>     }
> }
> ```

> ### Defining Critical Apps
>
> You can now define apps as critical for the platform by using the `"critical"` top-level key in the [app definition](https://docs.interop.io/desktop/developers/configuration/application/index.md). If a critical app fails to load, the platform will shutdown and display an error message:
>
> ```json
> {
>     "name": "my-app",
>     "type": "window",
>     "critical": true,
>     "details": {
>         "url": "https://my-app.com"
>     }
> }
> ```
>
> To mark dynamically started apps or dynamically created io.Connect Windows as critical, use the `critical` property of the [`ApplicationStartOptions`](https://docs.interop.io/desktop/reference/javascript/app%20management/applicationstartoptions/index.md) or the [`WindowCreateOptions`](https://docs.interop.io/desktop/reference/javascript/windows/windowcreateoptions/index.md) objects respectively:
>
> ```javascript
> // Starting a critical app.
> const appName = "my-app";
> const appStartOptions = { critical: true };
>
> await io.appManager.application(appName).start(null, appStartOptions);
>
> // Creating a critical window.
> const windowName = "my-window";
> const windowURL = "https://example.com";
> const windowCreateOptions = { critical: true };
>
> await io.windows.open(windowName, windowURL, windowCreateOptions);
> ```

> ### Platform Cache
>
> To set the location for storing files cached by **io.Connect Desktop** (preload files, injected CSS files, and app icons), use the `"location"` property of the `"platformCache"` object under the `"folders"` top-level key in the `system.json` [system configuration](https://docs.interop.io/desktop/developers/configuration/system/index.md) file of **io.Connect Desktop**:
>
> ```json
> {
>     "folders": {
>         "platformCache": {
>             "location": "%LocalAppData%/my-custom-cache-location/"
>         }
>     }
> }
> ```
>
> The `"platformCache"` object has the following properties:
>
> | Property | Type | Description |
> |----------|------|-------------|
> | `"location"` | `string` | Absolute path determining the location for storing the cached files. The platform will use this location to cache all preload scripts and injected CSS files defined in the system configuration and in the app definition files, as well as all app icons. You can use [environment variables](https://docs.interop.io/desktop/developers/configuration/overview/index.md#environment_variables). Defaults to `"%LocalAppData%/interop.io/io.Connect Desktop/UserData/%GLUE-ENV%-%GLUE-REGION%/cache"`. |

> ### Channel Selector UI
>
> The Channel Selector UI now displays indicators when the window publishes data to a Channel, or only receives data from it. This functionality is available only for [web groups](https://docs.interop.io/desktop/capabilities/windows/window-management/overview/index.md#window_groups-web_groups) and when a [directional Channel Selector](https://docs.interop.io/desktop/capabilities/data-sharing/channels/overview/index.md#channel_selector) is enabled:
>
> ![Channel Selector Direction](https://docs.interop.io/desktop/images/channels/channel-selector-direction.png)

> ### Channels API
>
> The `data` object in a [`ChannelContext`](https://docs.interop.io/desktop/reference/javascript/channels/channelcontext/index.md) 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:
>
> ```javascript
> // 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()`](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md#API-publish) method of the Channels API now accepts also a [`PublishOptions`](https://docs.interop.io/desktop/reference/javascript/channels/publishoptions/index.md) 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:
>
> ```javascript
> // 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()`](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md#API-get), [`getMy()`](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md#API-getMy), [`subscribe()`](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md#API-subscribe), and [`subscribeFor()`](https://docs.interop.io/desktop/reference/javascript/channels/api/index.md#API-subscribeFor) methods for retrieving and subscribing to Channel contexts now accept an [`FDC3Options`](https://docs.interop.io/desktop/reference/javascript/channels/fdc3options/index.md) 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:
>
> ```javascript
> // 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);
> ```

> ### FDC3 Context Types
>
> 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](https://docs.interop.io/desktop/getting-started/fdc3-compliance/index.md#channels-using_fdc3_contexts_in_nonfdc3_apps) in non-FDC3 interop-enabled apps.

## Improvements & Bug Fixes

> - Upgraded to Electron 32.2.1 (Chromium 128).
>
> - Increased the default timeout for the **io.Connect Desktop** [splash screen](https://docs.interop.io/desktop/getting-started/how-to/rebrand-io-connect/user-interface/index.md#splash_screen) from 2000 to 5000 milliseconds.
>
> - Improved the behavior of the native file drag and drop functionality.
>
> - Improved handling of creating web group windows when the display resolution is different from 100%.
>
> - Fixed custom tray appearing at wrong location when its window is resized.
>
> - Fixed not restoring the Workspaces Frame in the correct state.
>
> - Fixed the behavior of the `"combineIcons"` property in the app definition for combining same app icons into a single icon in the Windows taskbar.
