Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empower platform devs to create an unrestricted notification service #682

Merged
merged 3 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions how-to/workspace-platform-starter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- 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.
- 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,43 +574,73 @@ 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<AppIdentifier> {
if (isEmpty(fdc3OpenOptions?.app)) {
logger.error("A request to fdc3.open did not pass an fdc3OpenOptions object");
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)) {
Expand All @@ -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;
Expand Down Expand Up @@ -685,6 +720,9 @@ export function interopOverride(
}

if (!isEmpty(appId)) {
if (focusApp && !isEmpty(platformIdentities)) {
await bringToFront(requestedApp, platformIdentities);
}
return { appId, instanceId };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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)) {
Expand All @@ -179,5 +187,5 @@ export async function getNotificationClient(
);
clientOptions.icon = notificationsProviderOptions?.icon;
}
return new NotificationClient(clientOptions, notificationPlatformId);
return new NotificationClientImplementation(clientOptions, notificationPlatformId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
Loading