From 2afe54cb4e981edd968cddd31218119de16ec4e1 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 9 Sep 2024 10:03:49 +0100 Subject: [PATCH] Added platform override example and fixes (#745) * Added platform override example and fixes Added an example platform override module that can update saved urls if the app definition changes or show an access denied page if the user no longer has access to the app. Updated the launch snapshot logic so that view ids are consistently returned. Updated page logic to ensure that page view ids with guids are regenerated to prevent clashes (where a view is moved from a page to another window and then the page is relaunched). Added docs and updated the changelog. * Removed the name as this snapshot is shared with other samples. --- .../workspace-platform-starter/CHANGELOG.md | 5 + .../client/src/framework/launch.ts | 66 ++-- .../README.md | 81 +++++ .../index.ts | 9 + .../platform-override.ts | 289 ++++++++++++++++++ .../shapes.ts | 9 + .../platform/wps-platform-override.ts | 27 +- .../client/webpack.config.js | 22 ++ ...how-to-customize-your-platform-override.md | 1 + .../contact-overview.snapshot.fin.json | 1 + .../views/platform/no-access/no-access.html | 43 +++ .../views/platform/no-access/no-access.js | 25 ++ .../public/manifest.fin.json | 33 +- 13 files changed, 562 insertions(+), 49 deletions(-) create mode 100644 how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/README.md create mode 100644 how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/index.ts create mode 100644 how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/platform-override.ts create mode 100644 how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/shapes.ts create mode 100644 how-to/workspace-platform-starter/public/common/views/platform/no-access/no-access.html create mode 100644 how-to/workspace-platform-starter/public/common/views/platform/no-access/no-access.js diff --git a/how-to/workspace-platform-starter/CHANGELOG.md b/how-to/workspace-platform-starter/CHANGELOG.md index 13e2be7994..14e8a1c878 100644 --- a/how-to/workspace-platform-starter/CHANGELOG.md +++ b/how-to/workspace-platform-starter/CHANGELOG.md @@ -7,6 +7,11 @@ - Updated Snap to 0.4.1 - Updated SnapProvider configuration so that it can support all snap server options and not just the show debug window setting. The showDebugWindow setting has been removed but there is backwards compatibility if it is specified in JSON. Please see [how to configure snap](./docs/how-to-configure-snap.md) for the latest configuration. - You can now specify a path to a snap exe instead of relying on the path of an app asset if you do have it in a specific installed in a specific location. +- Updated apply snapshot logic so that it always returns all the view ids that were created. +- Fixed bug where you could have a page with apps that is saved, the apps are moved and the page is closed without saving. Launching the page would move the apps from the other windows as the ids were not unique. The name of views that represent apps now behave like platform views where the guid (if it exists) is updated. This behavior now reflects the behavior of duplicate page. +- Snapshots that contain apps can now list the name as appId/guid (similar to how they are launched in a layout). If a guid is detected the snapshot entries are updated when applied (to avoid clashes if you launch multiple instances). +- Fixed an issue where an empty object was being returned (instead of undefined) when getPage was called and it didn't exist. +- Added an example of a platform override module that validates app urls and whether the app is still supported in workspaces, snapshots and pages. This is just an example (server side is a better place) to show how the behavior could work. [application-url-and-access-validator](./client/src/modules/platform-override/application-url-and-access-validator/) ## v19.0.0 diff --git a/how-to/workspace-platform-starter/client/src/framework/launch.ts b/how-to/workspace-platform-starter/client/src/framework/launch.ts index e1b6b1fd1e..893f2f764c 100644 --- a/how-to/workspace-platform-starter/client/src/framework/launch.ts +++ b/how-to/workspace-platform-starter/client/src/framework/launch.ts @@ -11,13 +11,7 @@ import { launchConnectedApp } from "./connections"; import * as endpointProvider from "./endpoint"; import { createLogger } from "./logger-provider"; import { MANIFEST_TYPES } from "./manifest-types"; -import { - bringViewToFront, - bringWindowToFront, - doesViewExist, - doesWindowExist, - findViewNames -} from "./platform/browser"; +import { bringViewToFront, bringWindowToFront, doesViewExist, doesWindowExist } from "./platform/browser"; import type { NativeLaunchOptions, PlatformApp, @@ -794,33 +788,20 @@ async function launchSnapshot(snapshotApp: PlatformApp): Promise(layout: T): T { + return JSON.parse( + JSON.stringify(layout, (_, nestedValue) => { + // check to ensure that we have a name field and that we also have a url field in this object (in case name was added to a random part of the layout) + if (isStringValue(nestedValue?.name) && !isEmpty(nestedValue.url)) { + if (/\/[\d,a-z-]{36}$/.test(nestedValue.name)) { + nestedValue.name = nestedValue.name.replace(/([\d,a-z-]{36}$)/, randomUUID()); + } + // case: internal-generated-view- + if (/-[\d,a-z-]{36}$/.test(nestedValue.name)) { + nestedValue.name = nestedValue.name.replace(/(-[\d,a-z-]{36}$)/, randomUUID()); + } + } + return nestedValue as unknown; + }) + ); +} diff --git a/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/README.md b/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/README.md new file mode 100644 index 0000000000..f6c0a374ec --- /dev/null +++ b/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/README.md @@ -0,0 +1,81 @@ +# Application Url and Access Validator + +## What is this? + +This is an example and this type of logic is best placed on the server (to reduce load on the client) but we are giving a client example to show you a simple example and to demonstrate how platform overrides can be used for very specific use cases. +This module is off by default but you can turn it on to see quick examples. + +This example is related to the following scenario: + +- I (Platform Owner) have a list of apps - those apps might be a view, window or a snapshot that is a collection of apps +- I allow my users to save Pages, Workspaces or share a Page or Workspace + +What happens when: + +- The saved page or workspace is loaded but the url for a particular app e.g. companyx.com/v1/app.html has been updated to companyx.com/v2/app.html? +- The user (or the user that has received the shared page/workspace) no longer has access to an app (or never did in the case of sharing a page/workspace)? +- There is one app (e.g. Salesforce) which might be an app in my directory with a starting point url but the app can be launched with different urls (e.g. pointing to a company or user) and I don't want the url to change in that scenario when the page or workspace is loaded. + +## What does it do? + +It provides an override for applySnapshot (used when loading snapshots and workspaces) and getPage (used for fetching a page to add it to a window). + +- Each override function calls a common function that goes through the data finding view or window entries that are listed as apps. +- If it is an app then it determines whether or not the app allows the apps url to be updatable (then it leaves the last url saved) or if the app url is overridden (then it takes the overridden url rather than getting it from a manifest or inline manifest). +- If it is an app that is not present in the app directory then it replaces the url with the defined (through module settings) no access page and passes the appId using customData (so the target page can decide what to do with that information). + +## How is it configured? + +This example module is defined as a platform override module in a manifest or settings service: + +```json +{ + "id": "application-url-and-access-validator", + "icon": "http://localhost:8080/favicon.ico", + "title": "Application Url and Access Validator", + "description": "This is an example platform override module that shows how you could validate a saved page, workspace or an application snapshot (if it combines apps) to ensure that it is using the latest url for the applications used and that the user still has access to that app.", + "enabled": false, + "url": "http://localhost:8080/js/modules/platform-override/application-url-and-access-validator.bundle.js", + "data": { + "deniedAccessUrl": "http://localhost:8080/common/views/platform/no-access/no-access.html" + } +}, +``` + +This is defined in the modules section of the platformProvider settings. + +Order is important when it comes to platform and interop overrides. The request will hit the first entry in the array and every time super.x is called it will go to the next entry in the array until it hits the default implementation. + +For this example we want to intercept the getPage request **before** the default workspace platform starter implementation. This lets our module get the page from the workspace platform starter platform override module (which might call super.getPage to call the default implementation or it can return the page if it has been configured to store pages in a backend server). If this module was placed after the workspace platform starter module in the array and it was configured to save and get pages from a server then our implementation of getPage would never be called. + +## How can I test this? + +- You would enable this module in the manifest.fin.json file in the public folder. +- You would launch the platform using npm run client +- You would then launch the call app and some other apps through home (or store). +- You would use the browser to save a page e.g. 'page with call' and a workspace e.g. 'call wks'. + +### Access Denied + +- Navigate to the place where the call app is defined: [public/common/apps-contact.json](../../../../../public/common/apps-contact.json) +- You will find the call-app defined in the top of the list. Change the id of call-app to call-app-2 and save the file (we cache the app directory for 10 seconds by default so it should pick up the new changes). +- Close the page 'page with call' that has the call app. +- Save a second workspace e.g. 'no call wks' to make it the current workspace. +- Now launch the page 'page with call' -> You should see the page launch with the access denied view +- Switch workspaces to load 'call wks' -> You should see the workspace load and the access denied view show where you had saved it in the workspace. + +### App Url Changed + +- Switch back to the 'no call wks' so that you have a layout that doesn't have any apps that have had changes. +- Go back to the call app entry in [public/common/apps-contact.json](../../../../../public/common/apps-contact.json) and rename the appId so that it goes back to call-app. +- Update the manifest path for the call app from to . +- Navigate to the folder containing the call-app.view.fin.json file ([public/common/views/contact](../../../../../public/common/views/contact/)) and duplicate the file calling it call-app-v2.view.fin.json +- Update the url inside the new manifest call-app-v2.view.fin.json from to (just to make it easy to see the change). +- Now launch the page 'page with call' -> You should see the page launch with the google site shown +- Switch workspaces to load 'call wks' -> You should see the workspace load and the google page should show where you had saved the call app in the workspace. + +If inline-view or inline-window was used then the logic will fetch the url from the inline manifest as it doesn't need to do a fetch to get an external json file. + +## Where can I find out more about Platform Overrides + +[How To Customize Your Platform Override](../../../../../docs/how-to-customize-your-platform-override.md) diff --git a/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/index.ts b/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/index.ts new file mode 100644 index 0000000000..331e874b52 --- /dev/null +++ b/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/index.ts @@ -0,0 +1,9 @@ +import type { ModuleImplementation, ModuleTypes } from "workspace-platform-starter/shapes/module-shapes"; +import { ApplicationUrlAndAccessValidator } from "./platform-override"; + +/** + * Define the entry points for the module. + */ +export const entryPoints: { [type in ModuleTypes]?: ModuleImplementation } = { + platformOverride: new ApplicationUrlAndAccessValidator() +}; diff --git a/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/platform-override.ts b/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/platform-override.ts new file mode 100644 index 0000000000..8ecebcb59d --- /dev/null +++ b/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/platform-override.ts @@ -0,0 +1,289 @@ +// eslint-disable-next-line max-classes-per-file +import type OpenFin from "@openfin/core"; +import type { Page, WorkspacePlatformProvider } from "@openfin/workspace-platform"; +import type { PlatformApp } from "workspace-platform-starter/shapes"; +import type { Logger, LoggerCreator } from "workspace-platform-starter/shapes/logger-shapes"; +import type { ModuleDefinition } from "workspace-platform-starter/shapes/module-shapes"; +import type { + PlatformOverride, + PlatformOverrideHelpers, + PlatformOverrideOptions +} from "workspace-platform-starter/shapes/platform-shapes"; +import type { ApplicationUrlAndAccessValidatorOptions } from "./shapes"; + +/** + * Implementation for the application validator platform override. + */ +export class ApplicationUrlAndAccessValidator + implements PlatformOverride +{ + /** + * The module definition including settings. + * @internal + */ + private _definition: ModuleDefinition | undefined; + + /** + * The logger for displaying information from the module. + * @internal + */ + private _logger?: Logger; + + /** + * Helper methods for the module. + * @internal + */ + private _helpers: PlatformOverrideHelpers | undefined; + + /** + * Initialize the module. + * @param definition The definition of the module from configuration include custom options. + * @param loggerCreator For logging entries. + * @param helpers Helper methods for the module to interact with the application core. + * @returns Nothing. + */ + public async initialize( + definition: ModuleDefinition, + loggerCreator: LoggerCreator, + helpers: PlatformOverrideHelpers + ): Promise { + this._definition = definition; + this._logger = loggerCreator("ApplicationUrlAndAccessValidatorPlatformOverride"); + this._helpers = helpers; + + this._logger.info("Initializing"); + + // TODO: Add code here to allocate any module resources + // You can access the configured options e.g. definition.data?.exampleProp + } + + /** + * Close down any resources being used by the module. + * @returns Nothing. + */ + public async closedown(): Promise { + this._logger?.info("Closedown"); + + // TODO: Add code here to free up any module resources + } + + /** + * Get the override constructor for the platform override (useful if you wish this implementation to be layered with other implementations and passed to the platform's initialization object as part of an array). + * @param options The options for the platform override defined as part of the platform. + * @returns The override constructor to be used in an array. + */ + public async getConstructorOverride( + options: PlatformOverrideOptions + ): Promise> { + return (Base: OpenFin.Constructor) => { + // use settings passed through the module definition in your override or the default options passed with the function call + // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-restricted-syntax + if (this._helpers === undefined) { + throw new Error( + "Helpers are required for the the application validator platform override. Please ensure that the platform has been initialized correctly." + ); + } + const moduleData = this._definition?.data ?? {}; + const logger = this._logger; + const helpers = this._helpers; + const utilClient = helpers.getUtilClient(); + const cachedManifestTokens: { [key: string]: { manifest: string; url: string } } = {}; + // caching the functions as it is called multiple times in the applySnapshot override. + const isStringValue = utilClient.general.isStringValue; + const isEmpty = utilClient.general.isEmpty; + const randomUUID = utilClient.general.randomUUID; + + /** + * Extend the Platform Override. + */ + return class CustomPlatformOverride extends Base { + /** + * Constructor for the interop override. + */ + constructor() { + super(); + // this is just an example to show a reference to the options, module data and local reference to the passed helpers. + logger?.info( + `Options passed: ${JSON.stringify(options)} and module data: ${JSON.stringify(moduleData)} with session id: ${helpers.sessionId}` + ); + } + + /** + * Implementation for getting a single page in persistent storage. + * @param id The id of the page. + * @returns a page object or undefined if page doesn't exist. + */ + public async getSavedPage(id: string): Promise { + // get the page from the default platform implementation or an override that stores it in an alternative location. + logger?.info(`Requesting saved page with id: ${id}`); + const page = await super.getSavedPage(id); + // process page layout to ensure that any apps are using the correct urls and access should still be allowed. + if (page) { + logger?.info(`Processing page layout for page with id: ${id}`); + const processedLayout: string = await this.validateAppUrlAndAccess(page.layout); + page.layout = JSON.parse(processedLayout); + } + return page; + } + + /** + * An apply snapshot override to check for updated urls and apps that are no longer permissible. If possible you would want this type of logic on the server so it is + * only applied to saved workspaces or snapshots (pages are managed by getPage). This is just an example to show the concept. + * @param payload the payload for the snapshot + * @param identity the identity of the entity that called applySnapshot + * @returns nothing + */ + public async applySnapshot( + payload: OpenFin.ApplySnapshotPayload, + identity?: OpenFin.Identity + ): Promise { + // this is an example of how to override the applySnapshot method to check for updated urls and apps that are no longer permissible. + logger?.info("Processing snapshot windows for app url changes or access updates"); + const processedWindows: string = await this.validateAppUrlAndAccess(payload.snapshot.windows); + + payload.snapshot.windows = JSON.parse(processedWindows); + logger?.info("Passing processed snapshot to base applySnapshot."); + return super.applySnapshot(payload, identity); + } + + /** + * This function goes through the payload and checks for app entries and if found checks to see that they + * have the latest url for the app if needed and also checks to see if they still have permission for the app. + * @param payload The payload to validate. + * @returns The validated payload to be parsed as an object. + */ + private async validateAppUrlAndAccess(payload: unknown): Promise { + const appCache: { [key: string]: { exists: boolean; url?: string } } = {}; + const manifestTokens: { [key: string]: { manifest: string; originalUrl: string } } = {}; + let appEntry: { exists: boolean; url?: string } | undefined; + const apps: PlatformApp[] = []; + if (helpers.getApps) { + // rather than caching the apps once for the lifetime of the platform you are letting the app service manage the caching of the apps + // so that this request would be coming from a cache. + apps.push(...(await helpers.getApps())); + } + let processedPayload: string = JSON.stringify(payload, (_, nestedValue) => { + if (Array.isArray(nestedValue)) { + return nestedValue; + } + // check to ensure that we have a name field and that we also have a url field in this object (in case name was added to a random part of the layout) + if ( + isStringValue(nestedValue?.name) && + !nestedValue.name.startsWith("internal-generated-") && + !isEmpty(nestedValue.url) + ) { + const appId = nestedValue.name.split("/")[0]; + appEntry = { exists: false }; + let app: PlatformApp | undefined; + if (!appCache[appId]) { + app = apps.find((a) => a.appId === appId); + if (app) { + appEntry = { exists: true }; + if (app.launchPreference?.options?.type === "window") { + if ( + Array.isArray(app.launchPreference.options?.updatable) && + app.launchPreference.options?.updatable.findIndex((update) => update.name === "url") > + -1 + ) { + // if an app is marked as having an updatable url (e.g. a crm system entry with an initial app url that can be changed) + // then we keep the original url but mark that access to the app still exists. + appEntry = { exists: true }; + } else if (app.launchPreference.options.window?.url) { + // there is an override for the url provided in the manifest use this instead of the source entry. + appEntry = { url: app.launchPreference.options.window.url, exists: true }; + } + } else if (app.launchPreference?.options?.type === "view") { + if ( + Array.isArray(app.launchPreference.options?.updatable) && + app.launchPreference.options?.updatable.findIndex((update) => update.name === "url") > + -1 + ) { + // if an app is marked as having an updatable url (e.g. a crm system entry with an initial app url that can be changed) + // then we keep the original url but mark that access to the app still exists. + appEntry = { exists: true }; + } else if (app.launchPreference.options.view?.url) { + // there is an override for the url provided in the manifest use this instead of the source entry. + appEntry = { url: app.launchPreference.options.view.url, exists: true }; + } + } else if ( + (app?.manifestType === "inline-view" || app?.manifestType === "inline-window") && + (app.manifest as { url: string }).url + ) { + appEntry = { url: (app.manifest as { url: string }).url, exists: true }; + } else if (app?.manifestType === "view" || app?.manifestType === "window") { + const token = `[[${app.appId}]]`; + if (!isEmpty(manifestTokens[token])) { + const t = { manifest: app.manifest, originalUrl: nestedValue.url }; + manifestTokens[token] = t; + } + appEntry = { url: `[[${app.appId}]]`, exists: true }; + } + } else { + appEntry = { exists: false }; + } + appCache[appId] = appEntry; + } else { + appEntry = appCache[appId]; + } + if (appEntry.exists) { + nestedValue.url = appEntry.url ?? nestedValue.url; + } else if (moduleData.deniedAccessUrl) { + nestedValue.url = moduleData.deniedAccessUrl; + nestedValue.customData = { appId }; + nestedValue.name = `internal-generated-${randomUUID()}`; + } + } + return nestedValue as unknown; + }); + + const tokens = Object.keys(manifestTokens); + if (tokens.length > 0) { + // we only want to fetch the url for the manifest once regardless of how many app entries there are in the snapshot. + // we are caching in this example assuming that manifest urls are also versioned like app urls will be. In a real implementation + // this would be done on the server to remove the load from the client or inline views/windows would be used or a caching mechanism + // that could be cleared when the app data changes. + for (const token of tokens) { + const cachedManifestToken = cachedManifestTokens[token]; + let url = manifestTokens[token].originalUrl; + if ( + !isEmpty(cachedManifestToken) && + cachedManifestToken.manifest === manifestTokens[token].manifest + ) { + url = cachedManifestToken.url; + } else { + url = await this.getUrlFromManifest( + manifestTokens[token].manifest, + manifestTokens[token].originalUrl + ); + cachedManifestTokens[token] = { manifest: manifestTokens[token].manifest, url }; + } + processedPayload = processedPayload.replaceAll(token, url); + } + } + + return processedPayload; + } + + /** + * Fetch the url from the manifest or returns the original url. + * @param manifest The manifest to fetch the url from. + * @param originalUrl The original url to return if the manifest does not have a url. + * @returns The url from the manifest or the original url. + */ + private async getUrlFromManifest(manifest: string, originalUrl: string): Promise { + try { + const fetchedManifest = await fin.System.fetchManifest(manifest); + if (fetchedManifest.url) { + return fetchedManifest.url; + } + logger?.warn(`No url found in manifest for ${originalUrl}`); + return originalUrl; + } catch (error) { + logger?.error(`Error fetching manifest for ${originalUrl}`, error); + return originalUrl; + } + } + }; + }; + } +} diff --git a/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/shapes.ts b/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/shapes.ts new file mode 100644 index 0000000000..2c264c5277 --- /dev/null +++ b/how-to/workspace-platform-starter/client/src/modules/platform-override/application-url-and-access-validator/shapes.ts @@ -0,0 +1,9 @@ +/** + * Options for the application validator platform override. + */ +export interface ApplicationUrlAndAccessValidatorOptions { + /** + * Denied access page. + */ + deniedAccessUrl?: string; +} diff --git a/how-to/workspace-platform-starter/client/src/modules/platform-override/wps-platform-override/platform/wps-platform-override.ts b/how-to/workspace-platform-starter/client/src/modules/platform-override/wps-platform-override/platform/wps-platform-override.ts index af44b45515..451089476c 100644 --- a/how-to/workspace-platform-starter/client/src/modules/platform-override/wps-platform-override/platform/wps-platform-override.ts +++ b/how-to/workspace-platform-starter/client/src/modules/platform-override/wps-platform-override/platform/wps-platform-override.ts @@ -495,6 +495,7 @@ export async function getConstructorOverride( // you can add your own custom implementation here if you are storing your pages // in non-default location (e.g. on the server instead of locally) logger.info(`Checking for custom page storage with endpoint id: ${PAGE_ENDPOINT_ID_GET}`); + let pageToReturn: Page | undefined; if (endpointProvider.hasEndpoint(PAGE_ENDPOINT_ID_GET)) { logger.info(`Getting saved page from custom storage for page id: ${id}`); const pageResponse = await endpointProvider.requestResponse< @@ -507,16 +508,26 @@ export async function getConstructorOverride( if (pageResponse) { logger.info(`Returning saved page from custom storage for page id: ${id}`); const defaultOpts = await buildDefaultOptions(); - return mapPlatformPageFromStorage(pageResponse.payload, defaultOpts); + pageToReturn = mapPlatformPageFromStorage(pageResponse.payload, defaultOpts); + } else { + logger.warn(`No response getting saved page from custom storage for page id: ${id}`); + return; } - - logger.warn(`No response getting saved page from custom storage for page id: ${id}`); - return {} as Page; + } else { + logger.info(`Getting saved page with id ${id} from default storage`); + pageToReturn = await super.getSavedPage(id); + if (pageToReturn) { + logger.info(`Returning saved page with id ${id} from default storage`); + } else { + logger.warn(`No response getting saved page from default storage for page id: ${id}`); + return; + } + } + pageToReturn.layout = duplicateLayout(pageToReturn?.layout); + if (Array.isArray(pageToReturn.panels) && pageToReturn.panels.length > 0) { + pageToReturn.panels = duplicateLayout(pageToReturn.panels); } - logger.info(`Getting saved page with id ${id} from default storage`); - const pageResponse = await super.getSavedPage(id); - logger.info(`Returning saved page with id ${id} from default storage`); - return pageResponse; + return pageToReturn; } /** diff --git a/how-to/workspace-platform-starter/client/webpack.config.js b/how-to/workspace-platform-starter/client/webpack.config.js index 71632aad49..e8318c417b 100644 --- a/how-to/workspace-platform-starter/client/webpack.config.js +++ b/how-to/workspace-platform-starter/client/webpack.config.js @@ -58,6 +58,28 @@ const configs = [ experiments: { outputModule: true } + }, + { + entry: './client/src/modules/platform-override/application-url-and-access-validator/index.ts', + devtool: 'source-map', + module: { + rules: [loaderRule] + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + alias + }, + externals: { fin: 'fin' }, + output: { + filename: 'application-url-and-access-validator.bundle.js', + library: { + type: 'module' + }, + path: path.resolve(__dirname, '..', 'public', 'js', 'modules', 'platform-override') + }, + experiments: { + outputModule: true + } } ]; diff --git a/how-to/workspace-platform-starter/docs/how-to-customize-your-platform-override.md b/how-to/workspace-platform-starter/docs/how-to-customize-your-platform-override.md index ede58be2b8..039af14135 100644 --- a/how-to/workspace-platform-starter/docs/how-to-customize-your-platform-override.md +++ b/how-to/workspace-platform-starter/docs/how-to-customize-your-platform-override.md @@ -85,5 +85,6 @@ This now opens up the capability to add your own logic to the platform (you want ## Source Reference - [Default Workspace Starter Platform Override Module](../client/src/modules/platform-override/wps-platform-override/) +- [Example Workspace Platform Override That Validates App Urls and Access](../client/src/modules/platform-override/application-url-and-access-validator/) [<- Back to Table Of Contents](../README.md) diff --git a/how-to/workspace-platform-starter/public/common/snapshots/contact-overview.snapshot.fin.json b/how-to/workspace-platform-starter/public/common/snapshots/contact-overview.snapshot.fin.json index c9401bc04d..cda29c8b0a 100644 --- a/how-to/workspace-platform-starter/public/common/snapshots/contact-overview.snapshot.fin.json +++ b/how-to/workspace-platform-starter/public/common/snapshots/contact-overview.snapshot.fin.json @@ -186,6 +186,7 @@ { "type": "component", "componentName": "view", + "componentState": { "bounds": { "x": 1, diff --git a/how-to/workspace-platform-starter/public/common/views/platform/no-access/no-access.html b/how-to/workspace-platform-starter/public/common/views/platform/no-access/no-access.html new file mode 100644 index 0000000000..27c68218e8 --- /dev/null +++ b/how-to/workspace-platform-starter/public/common/views/platform/no-access/no-access.html @@ -0,0 +1,43 @@ + + + + + + Access Denied + + + + + + + + +
+
+

Application Access

+

Application is no longer accessible

+
+
+ OpenFin +
+
+
+
+
+

+ Application Id: + +

+
+ You no longer have access to this application. Please contact your administrator for more + information. +
+
+
+
+ + diff --git a/how-to/workspace-platform-starter/public/common/views/platform/no-access/no-access.js b/how-to/workspace-platform-starter/public/common/views/platform/no-access/no-access.js new file mode 100644 index 0000000000..54f665bc2a --- /dev/null +++ b/how-to/workspace-platform-starter/public/common/views/platform/no-access/no-access.js @@ -0,0 +1,25 @@ +const appId = document.querySelector('#appId'); + +/** + * Init the content. + */ +async function init() { + let deniedAppId = 'Unknown'; + if (window.fin) { + const options = await fin.me.getOptions(); + if (options?.customData?.appId !== undefined) { + deniedAppId = options.customData.appId; + } + if (appId) { + appId.textContent = deniedAppId; + } + } +} + +document.addEventListener('DOMContentLoaded', () => { + try { + init(); + } catch (error) { + console.error(error); + } +}); diff --git a/how-to/workspace-platform-starter/public/manifest.fin.json b/how-to/workspace-platform-starter/public/manifest.fin.json index d28073c3ae..b771e5b0bc 100644 --- a/how-to/workspace-platform-starter/public/manifest.fin.json +++ b/how-to/workspace-platform-starter/public/manifest.fin.json @@ -100,6 +100,17 @@ "initialLanguage": "en-US" }, "modules": [ + { + "id": "application-url-and-access-validator", + "icon": "http://localhost:8080/favicon.ico", + "title": "Application Url and Access Validator", + "description": "This is an example platform override module that shows how you could validate a saved page, workspace or an application snapshot (if it combines apps) to ensure that it is using the latest url for the applications used and that the user still has access to that app.", + "enabled": true, + "url": "http://localhost:8080/js/modules/platform-override/application-url-and-access-validator.bundle.js", + "data": { + "deniedAccessUrl": "http://localhost:8080/common/views/platform/no-access/no-access.html" + } + }, { "id": "default-wps-platform", "icon": "http://localhost:8080/favicon.ico", @@ -187,17 +198,6 @@ ] }, "modules": [ - { - "id": "default-wps-interop", - "icon": "http://localhost:8080/favicon.ico", - "title": "Workspace Platform Starter Interop Broker", - "description": "This is the implementation included in workspace platform starter but it is now exposed as a module to allow for easy replacement.", - "enabled": true, - "url": "http://localhost:8080/js/modules/interop-override/wps-interop-override.bundle.js", - "data": { - "loggerName": "WpsInteropOverride" - } - }, { "id": "openfin-cloud-interop", "icon": "http://localhost:8080/favicon.ico", @@ -213,6 +213,17 @@ "sourceDisplayName": "", "sourceId": "" } + }, + { + "id": "default-wps-interop", + "icon": "http://localhost:8080/favicon.ico", + "title": "Workspace Platform Starter Interop Broker", + "description": "This is the implementation included in workspace platform starter but it is now exposed as a module to allow for easy replacement.", + "enabled": true, + "url": "http://localhost:8080/js/modules/interop-override/wps-interop-override.bundle.js", + "data": { + "loggerName": "WpsInteropOverride" + } } ] }