From b57fefb2556ee9a203bdecac77dd0a685d7ab8a5 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 10 Feb 2024 21:13:30 +0000 Subject: [PATCH 1/2] Add support for fdc3 open with instanceId --- .../workspace-platform-starter/CHANGELOG.md | 1 + .../src/framework/platform/interopbroker.ts | 76 ++++++++++++++----- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/how-to/workspace-platform-starter/CHANGELOG.md b/how-to/workspace-platform-starter/CHANGELOG.md index 46e9d2bc13..0df605c9d7 100644 --- a/how-to/workspace-platform-starter/CHANGELOG.md +++ b/how-to/workspace-platform-starter/CHANGELOG.md @@ -8,6 +8,7 @@ - Fixed an issue where the order of entries within dock would change when toggling between light and dark theme. - Improved performance when switching themes - Fixed an issue where the order of Workspace component entries within dock may change when toggling between light and dark theme. +- FDC3 2.0 Open improvement - You can now specify instance id if you wish to open an existing instance of an app and optionally pass it context. ## v16.1.0 diff --git a/how-to/workspace-platform-starter/client/src/framework/platform/interopbroker.ts b/how-to/workspace-platform-starter/client/src/framework/platform/interopbroker.ts index 0b68ed6287..ff79a34065 100644 --- a/how-to/workspace-platform-starter/client/src/framework/platform/interopbroker.ts +++ b/how-to/workspace-platform-starter/client/src/framework/platform/interopbroker.ts @@ -574,7 +574,7 @@ export function interopOverride( * @returns The application identifier. */ public async fdc3HandleOpen( - fdc3OpenOptions: { app: PlatformApp | string; context: OpenFin.Context }, + fdc3OpenOptions: { app: (PlatformApp & AppIdentifier) | string; context: OpenFin.Context }, clientIdentity: OpenFin.ClientIdentity ): Promise { if (isEmpty(fdc3OpenOptions?.app)) { @@ -582,35 +582,65 @@ export function interopOverride( throw new Error(ResolveError.NoAppsFound); } - const requestedId = isString(fdc3OpenOptions.app) - ? fdc3OpenOptions.app - : fdc3OpenOptions.app.appId ?? fdc3OpenOptions.app.name; - const openAppIntent: OpenFin.Intent = { - context: fdc3OpenOptions.context, - name: "OpenApp", - metadata: { - target: { appId: requestedId } - } - }; logger.info( `A request to Open has been sent to the platform by uuid: ${clientIdentity?.uuid}, name: ${clientIdentity?.name}, endpointId: ${clientIdentity.endpointId} with passed context:`, fdc3OpenOptions.context ); try { - const isOpenByIntent = this._openOptions?.openStrategy === "intent"; - let appId: string | undefined; + let requestedId: string; let instanceId: string | undefined; + let platformIdentities: PlatformAppIdentifier[] | undefined; + let focusApp = false; + let appId: string | undefined; + + if (isString(fdc3OpenOptions.app)) { + requestedId = fdc3OpenOptions.app; + } else { + requestedId = fdc3OpenOptions.app.appId ?? fdc3OpenOptions.app.name; + instanceId = fdc3OpenOptions.app.instanceId; + } const requestedApp = await getApp(requestedId); if (isEmpty(requestedApp)) { throw new Error(OpenError.AppNotFound); } + if (!isEmpty(instanceId)) { + // an instance of an application was selected now look up the uuid and name + const allConnectedClients = await this.getAllClientInfo(); + const clientInfo = allConnectedClients.find( + (connectedClient) => connectedClient.endpointId === instanceId + ); + if (!isEmpty(clientInfo)) { + logger.info(`App Id: ${requestedId} and instance Id: ${instanceId} was provided and found.`); + // the connected instance is available + platformIdentities = [ + { + uuid: clientInfo.uuid, + name: clientInfo.name, + appId: requestedId, + instanceId + } + ]; + } else { + throw new Error(ResolveError.TargetInstanceUnavailable); + } + } + + const isOpenByIntent = this._openOptions?.openStrategy === "intent"; + if (isOpenByIntent) { + const openAppIntent: OpenFin.Intent = { + context: fdc3OpenOptions.context, + name: "OpenApp", + metadata: { + target: { appId: requestedId } + } + }; const result = await this.launchAppWithIntent( requestedApp, openAppIntent, - undefined, + instanceId, clientIdentity ); if (isString(result.source)) { @@ -620,13 +650,18 @@ export function interopOverride( instanceId = result.source.instanceId; } } else { - let launchPreference: LaunchPreference | undefined; - const bounds = await getWindowPositionUsingStrategy(this._windowPositionOptions, clientIdentity); - if (!isEmpty(bounds)) { - launchPreference = { bounds }; + if (isEmpty(platformIdentities)) { + let launchPreference: LaunchPreference | undefined; + const options = this._windowPositionOptions; + const bounds = await getWindowPositionUsingStrategy(options, clientIdentity); + if (!isEmpty(bounds)) { + launchPreference = { bounds }; + } + platformIdentities = await launch(requestedApp, launchPreference); + } else { + focusApp = true; } - const platformIdentities = await launch(requestedApp, launchPreference); if (!isEmpty(platformIdentities) && platformIdentities?.length > 0) { appId = platformIdentities[0].appId; const openTimeout: number | undefined = this._openOptions?.connectionTimeout; @@ -685,6 +720,9 @@ export function interopOverride( } if (!isEmpty(appId)) { + if (focusApp && !isEmpty(platformIdentities)) { + await bringToFront(requestedApp, platformIdentities); + } return { appId, instanceId }; } From 79d56eb6171547fcc7dfb809ff437b9067d3d02d Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Feb 2024 20:06:40 +0000 Subject: [PATCH 2/2] Empower platform devs to create an unrestricted notification service This option allows a platform dev to nominate one or more lifecycle modules (or other types) to have an unrestricted notification client (no icon, or in platform tab enforcement and the notifications are not scoped to prefix id or module id). This is achieved by listing the module in the notification clients section of the notification provider and setting it's restricted setting to false (default is true). --- how-to/workspace-platform-starter/CHANGELOG.md | 1 + .../src/framework/shapes/notification-shapes.ts | 8 ++++++++ ...nt.ts => notification-client-implementation.ts} | 4 ++-- .../src/framework/workspace/notifications.ts | 14 +++++++++++--- .../types/module/shapes/notification-shapes.d.ts | 7 +++++++ .../public/schemas/settings.schema.json | 4 ++++ .../public/schemas/wps.manifest.schema.json | 4 ++++ 7 files changed, 37 insertions(+), 5 deletions(-) rename how-to/workspace-platform-starter/client/src/framework/workspace/{notification-client.ts => notification-client-implementation.ts} (98%) diff --git a/how-to/workspace-platform-starter/CHANGELOG.md b/how-to/workspace-platform-starter/CHANGELOG.md index 0df605c9d7..a5d126dbc5 100644 --- a/how-to/workspace-platform-starter/CHANGELOG.md +++ b/how-to/workspace-platform-starter/CHANGELOG.md @@ -9,6 +9,7 @@ - Improved performance when switching themes - Fixed an issue where the order of Workspace component entries within dock may change when toggling between light and dark theme. - FDC3 2.0 Open improvement - You can now specify instance id if you wish to open an existing instance of an app and optionally pass it context. +- Add option to allow specific modules to get unrestricted notificationClients if they want to implement custom platform logic without importing notifications directly via the npm package. The notification client options in the notification provider now has a 'restricted' setting which can be set to false. ## v16.1.0 diff --git a/how-to/workspace-platform-starter/client/src/framework/shapes/notification-shapes.ts b/how-to/workspace-platform-starter/client/src/framework/shapes/notification-shapes.ts index d9fa0442aa..a30083fccb 100644 --- a/how-to/workspace-platform-starter/client/src/framework/shapes/notification-shapes.ts +++ b/how-to/workspace-platform-starter/client/src/framework/shapes/notification-shapes.ts @@ -98,6 +98,14 @@ export interface NotificationClientOptions extends NotificationClientDefaultOpti * Should this module have a notification client. Default is true. */ enabled?: boolean; + + /** + * Should this module's notification client be scoped to it's id or prefix and it's settings controlled (e.g. icon)? + * Default is true. + * This means it will only be able to clear and read notifications scoped to that id. + * If false then it will be get the standard notifications implementation that is not restricted. + */ + restricted?: boolean; } /** diff --git a/how-to/workspace-platform-starter/client/src/framework/workspace/notification-client.ts b/how-to/workspace-platform-starter/client/src/framework/workspace/notification-client-implementation.ts similarity index 98% rename from how-to/workspace-platform-starter/client/src/framework/workspace/notification-client.ts rename to how-to/workspace-platform-starter/client/src/framework/workspace/notification-client-implementation.ts index 49476aee9f..a98751e99c 100644 --- a/how-to/workspace-platform-starter/client/src/framework/workspace/notification-client.ts +++ b/how-to/workspace-platform-starter/client/src/framework/workspace/notification-client-implementation.ts @@ -16,14 +16,14 @@ import { import { createLogger } from "workspace-platform-starter/logger-provider"; import { isEmpty, randomUUID } from "workspace-platform-starter/utils"; import type { - NotificationClient as NotificationClientInterface, + NotificationClient, NotificationClientOptions, NotificationsEventMap } from "../shapes/notification-shapes"; /** * Notification client for use by modules to be able to create, remove and update notifications against a platform. */ -export class NotificationClient implements NotificationClientInterface { +export class NotificationClientImplementation implements NotificationClient { private readonly _options: NotificationClientOptions; private readonly _idPrefix: string; diff --git a/how-to/workspace-platform-starter/client/src/framework/workspace/notifications.ts b/how-to/workspace-platform-starter/client/src/framework/workspace/notifications.ts index a38b7eebe1..6b70c70e3a 100644 --- a/how-to/workspace-platform-starter/client/src/framework/workspace/notifications.ts +++ b/how-to/workspace-platform-starter/client/src/framework/workspace/notifications.ts @@ -5,10 +5,11 @@ import { getSettings } from "../settings"; import type { NotificationClientDefaultOptions, NotificationClientOptions, - NotificationProviderOptions + NotificationProviderOptions, + NotificationClient } from "../shapes/notification-shapes"; import { isEmpty } from "../utils"; -import { NotificationClient } from "./notification-client"; +import { NotificationClientImplementation } from "./notification-client-implementation"; const logger = createLogger("Notifications"); @@ -171,6 +172,13 @@ export async function getNotificationClient( return undefined; } + if (!isEmpty(listedClientOptions) && listedClientOptions.restricted === false) { + logger.info( + `The options passed to create a notification client requests a non-scoped notification client for the module with Id: ${options.id}` + ); + return Notifications; + } + const clientOptions = Object.assign(options, notificationClientDefaults, listedClientOptions ?? {}); if (isEmpty(clientOptions.icon)) { @@ -179,5 +187,5 @@ export async function getNotificationClient( ); clientOptions.icon = notificationsProviderOptions?.icon; } - return new NotificationClient(clientOptions, notificationPlatformId); + return new NotificationClientImplementation(clientOptions, notificationPlatformId); } diff --git a/how-to/workspace-platform-starter/client/types/module/shapes/notification-shapes.d.ts b/how-to/workspace-platform-starter/client/types/module/shapes/notification-shapes.d.ts index dcd6ee3dcd..631f6bc500 100644 --- a/how-to/workspace-platform-starter/client/types/module/shapes/notification-shapes.d.ts +++ b/how-to/workspace-platform-starter/client/types/module/shapes/notification-shapes.d.ts @@ -87,6 +87,13 @@ export interface NotificationClientOptions extends NotificationClientDefaultOpti * Should this module have a notification client. Default is true. */ enabled?: boolean; + /** + * Should this module's notification client be scoped to it's id or prefix and it's settings controlled (e.g. icon)? + * Default is true. + * This means it will only be able to clear and read notifications scoped to that id. + * If false then it will be get the standard notifications implementation that is not restricted. + */ + restricted?: boolean; } /** * Mapping of all notification events. diff --git a/how-to/workspace-platform-starter/public/schemas/settings.schema.json b/how-to/workspace-platform-starter/public/schemas/settings.schema.json index cba61ac89d..a54f941d92 100644 --- a/how-to/workspace-platform-starter/public/schemas/settings.schema.json +++ b/how-to/workspace-platform-starter/public/schemas/settings.schema.json @@ -3238,6 +3238,10 @@ "includeInPlatform": { "description": "Should this notification client be defaulted into the platform tab. Default is true\nunless a custom platform id is specified. If false then the current platform's id\nwill not be allowed if passed", "type": "boolean" + }, + "restricted": { + "description": "Should this module's notification client be scoped to it's id or prefix and it's settings controlled (e.g. icon)?\nDefault is true.\nThis means it will only be able to clear and read notifications scoped to that id.\nIf false then it will be get the standard notifications implementation that is not restricted.", + "type": "boolean" } }, "required": [ diff --git a/how-to/workspace-platform-starter/public/schemas/wps.manifest.schema.json b/how-to/workspace-platform-starter/public/schemas/wps.manifest.schema.json index 74b6363563..fb90b0bfd2 100644 --- a/how-to/workspace-platform-starter/public/schemas/wps.manifest.schema.json +++ b/how-to/workspace-platform-starter/public/schemas/wps.manifest.schema.json @@ -3020,6 +3020,10 @@ "includeInPlatform": { "description": "Should this notification client be defaulted into the platform tab. Default is true\nunless a custom platform id is specified. If false then the current platform's id\nwill not be allowed if passed", "type": "boolean" + }, + "restricted": { + "description": "Should this module's notification client be scoped to it's id or prefix and it's settings controlled (e.g. icon)?\nDefault is true.\nThis means it will only be able to clear and read notifications scoped to that id.\nIf false then it will be get the standard notifications implementation that is not restricted.", + "type": "boolean" } }, "required": ["id"],