UNPKG

@connectifi/agent-web

Version:

A simple web implementation of a connectifi agent

389 lines (277 loc) 16 kB
# connectifi-agent-web This module provides a basic implementation of a Connectifi Agent for all web clients including desktop integration platforms, electronjs, and vanilla browsers. It will bind to any [Connectifi interop service](https://connectifi.co) endpoint and expose a scoped FDC3 api. By default, the Agent will generate UX for standard interop functions such as color picking and intent resolution. It can also be run headless. ## instalation Install the module in your app: ```bash npm i @connectifi/agent-web ``` Include it in your bundle ```ts import {createAgent} from '@connectifi/agent-web'; ``` To bypass bundling, you can also load the Agent from unpkg.com and use directly as a script module: ```html <script type="module" > import { createAgent } from "https://unpkg.com/@connectifi/agent-web/"; createAgent( `https://[ME].connectifi.app`, `$[appName]@$[directoryName]`, ) .then((fdc3) => { // do fdc3 things... }); </script> ``` ## usage The module exposes a single function: _createAgent_ which returns a Promise that resolves with a standard [FDC3 2.0 API](https://fdc3.finos.org/docs/api/ref/DesktopAgent) (DesktopAgent) after successfully connecting to the specified interop service. The function takes 3 arguments: - _interopHost_ - the URL of the Connectifi service the app is targeting - _appId_ - the FDC3 appId for the app. Note: this **must** be in the format of _appName_@_directoryName_ (see security model below) - _config_ - optional configuration for the Agent including the following props: - _logger_ - a callback function for logging output from agent - _headless_ - boolean flag to run the Agent in headless mode (it will just make a connection and expose the FDC3 API) - _props_ - a collection of properties to configue the UI for the agent - _logoSrc_ - override the logo image **Note**: If you need an FDC3 1.2 intereface, you can use the [1.3.2 version](https://www.npmjs.com/package/@connectifi/agent-web/v/1.3.2) of this module. Simple creation example ```ts const fdc3Session = await createAgent('https://platform.connectifi.app, 'myApp@myDirectory'); //then use the standard FDC3 api const listener = await fdc3Session.addContextListener('fdc3.instrument', handleContext); ``` Example using the logger ```ts const logThat = (...params: any) => { console.log('logThat', ...params); const paramString = [...params].reduce((acc, p) => { acc += ` ${typeof p === 'object' ? JSON.stringify(p) : p}`; return acc; }); const newLog = `${new Date().toISOString()} - ${paramString}`; setLogs((prevLogs) => [...prevLogs, newLog]); }; const fdc3Session = await createAgent( 'https://platform.connectictifi.app', 'myApp@myDirectory', {logger: logThat} ); //then use the standard FDC3 api... ``` Example setting a custom logo ```ts const fdc3Session = await createAgent( 'https://platform.connectifi.app', 'myApp@myDirectory', { props: { logoSrc: 'https://mylogo.png', }, } ); //then use the standard FDC3 api... ``` **note:** If you are setting a custom logo, you'll get best results if the logo is: - over HTTPS - square - png format - white/light on a transparent background ## integrating with existing FDC3 patterns By scoping of the FDC3 API, the Connectifi Agent provides a superset of typical FDC3 functionality. Supporting the FDC3 global pattern used in Desktop Containers is simple to do as well. ### making a fdc3 global object ```ts const fdc3Global = async () => { //create the agent and assign to the window window.fdc3 = await createAgent( 'https://platform.connectifi.app', 'myApp@myDirectory' ); //fire the fdc3Ready event document.dispatchEvent(new CustomEvent('fdc3Ready', {})); }; ``` ### integrating into a preload Using the above code, it is simple to then package the global declaration as a preload script in any Electronjs-based project. ## bridging with an existing fdc3 global instance When setting the `bridgeGlobal` configuration flag to true, the agent will bridge with the globally declared FDC3 object in it's environment. Bridging behavior will: - if a Connectifi system/user channel is joined or left, the bridge will join or leave the global system/user channel of the same id (if it exists) - if a context is received over the currently joined channel, the bridge will perform an `fdc3.broadcast` on the global with the same context - if `raiseIntent` or `raiseIntentForContext` is called on the Connectifi scope, the Agent will also get the relevant intents from the FDC3 global environment and display them in the resolver as 'local' intents. **note:** this feature is a work in progress and the nuance of the behavior will continue to evolve along with support for bridging in different FDC3 environments. ## customizing the Agent The Connectifi Agent can be fully customized using the configuration object passed into the constructor. The *AgentConfig* interface: ```ts export interface AgentConfig { props?: FabProps; headless?: boolean; logLevel?: LogLevel; bridgeGlobal?: boolean; logger?: (...params: any) => void; onFDC3Ready?: (fdc3: DesktopAgent) => void; onSessionStarted?: (directory: DirectoryProps, username?: string) => void; onSessionError?: (errorMessage: string) => void; onSessionAuthRequired?: (directory: DirectoryProps) => void; onSignedIn?: (username: string) => void; onSignedOut?: () => void; onChannelJoined?: (channelId: string) => void; onChannelLeft?: () => void; onConnected?: (initialConnect: boolean) => void; onDisconnected?: (nextConnect?: number) => void; onWorkingChanged?: (workInProgress: boolean) => void; handleIntentResolution?: ( message: IntentResolutionMessage, callback: ResolveCallback, closeCallback: CloseCallback, ) => void; handleOpen?: (message: ConnectifiOpenMessage) => void; } ``` - props - These are UI-specific properties that configure the default ‘FAB’ UX provided by the Agent (see *UI Props* below) - headless - If set to *true* - no default UI is rendered by the agent. - logLevel - logging can be set to ‘debug’, ‘info’, or ‘silent’ - bridgeGlobal - Experimental feature: if set to *true* - the Agent will attach to the window.fdc3 object (e.g. if running in a desktop container) and join to it for intents resolution and broadcast of context. - logger - Specify a custom function to output log statements. ### UI Props ```ts logoSrc?: string; position?: ValidPositions; loginStyle?: LoginStyles; ``` - logoSrc - defines a custom location for the logo shown in the FAB - position - 'tl', 'ml', 'bl', 'tr', 'mr', or 'br’. Positions the FAB in top, bottom, middle, and right/left/center of the screen. ### UI Callbacks ```ts logger?: (...params: any) => void; onFDC3Ready?: (fdc3: DesktopAgent) => void; onSessionStarted?: (directory: DirectoryProps, username?: string) => void; onSessionError?: (errorMessage: string) => void; onSessionAuthRequired?: (directory: DirectoryProps) => void; onSignedIn?: (username: string) => void; onSignedOut?: () => void; onChannelJoined?: (channelId: string) => void; onChannelLeft?: () => void; onConnected?: (initialConnect: boolean) => void; onDisconnected?: (nextConnect?: number) => void; onWorkingChanged?: (workInProgress: boolean) => void; handleIntentResolution?: ( message: IntentResolutionMessage, callback: ResolveCallback, closeCallback: CloseCallback, ) => void; handleOpen?: (message: ConnectifiOpenMessage) => void; ``` #### handleIntentResolution This is called when an ambiguous list of intent results is pushed to the app for the end user to resolve. The callback will be provided with an IntentResolutionMessage data structure, along with a call back (ResolveCallback) to call when a user has selected on an app to resolve the intent with. ```tsx export interface IntentResolutionMessage { resolutionType: ResolutionType; context: Context; data: AppIntentResult | AppIntentResult[]; bridgeData?: AppIntent | AppIntent[]; } ``` The intent resolution message has the following properties: - resolutionType - ‘intent-resolver’ or ‘context-resolver’ depending on if the resolution is coming from a ***********raiseIntent*********** or ****************raiseIntentsForContext**************** call. - context - the FDC3 context object associated with the intent - data - either a single result (’intent-resolver’ type) or a list of results (’context-resolver’). See *AppIntentResult* below. - bridgeData - an optional set of results coming from the bridged fdc3 global context (Desktop Agent) if the *bridgeGlobal* property is set to true. See *AppIntentResult* below. ******************************AppIntentResult****************************** ```ts export interface AppIntentResult { intent: IntentMetadata; apps: Array<ConnectifiAppMetadata>; } ``` ```ts export interface ConnectifiAppMetadata extends AppMetadata { type: IntentResultType; id: string; url: string; instanceTitle: string; intents: Array<ConnectifiAppIntent>; proximity: number; useragent: string; browser?: string; device?: string; os?: string; lastUpdate: number; } ``` The AppIntentResult joins up the standard FDC3 IntentMetadata type with an Array of ConnectifiAppMetadata types. (i.e. an intent and the app results matching it). The *ConnectifiAppMetadata* type is an extension of the standard FDC3 AppMetadata type that provides some additional properties that be used to help the user disambiguate between applications. For example: - proximity - Starting with 0 - a number indicating the relative device and application proximity from the app that initiated the resolver. For example, if the target app is on the same device and running in the same application (e.g. Chrome) as the initiator, proximity is 0. The scoring is meant to aid sorting the list in order of apps closest to furthers from where the user is currently working. - browser - indicator of browser type the app is running in - device - indicator of the device the app is running on - os - indicator of the OS the app is running on - useragent - the raw user agent string for the app #### onChannelJoined Called when a channel is joined. Argument is the **id** of the channel joined. #### onChannelLeft Called when leaveCurrentChannel is executed. I.e. the user leaves the current channel but does not join another. #### handleOpen Called after *[fdc3.open](http://fdc3.open)* or an intent is resolved. In this case, the service sends a message to the client requesting it to launch a specific app. The message format is as follows: ```ts export interface ConnectifiOpenMessage { name?: string; url?: string; pendingId: string; } ``` Where *name* is the name of the app in the directory, *url* is the url to launch (for web applications), and *pendingId* is a nonce that will allow the newly launched app to retrieve any expected intent/context data. Note: name and url are optional depending on the type of app being launched. A typical handleOpen looks like this: ```ts const handleOpen = (message : ConnectifiOpenMessage ) => { if (data.url && data.pendingId) { window.open(data.url, data.pendingId as string); } } ``` Note the convention here of using the **********pendingId********** as the *name* for the new window - this is a convention picked up by the Agent - which will automatically use the name of the window to retrieve pending state for the new application. Other patterns can and will be used for non-browser clients. Also note, for most browsers, the [*window.open*](http://window.open) call must be sufficiently proximate to a end-user click to work. So, if you are building workflows that don’t have an opportunity for the end user to click on something before an app is launched - you will need to build some mechanism into the flow for browser (presenting the user with the link, etc). #### onWorkingChanged Called when the agent starts or stops "working" or "being busy". the boolean argument is the current state. #### onSignedIn Called after the user has successfully signed into the directory. Will be passed the username / identifier as an argument. #### onSignedOut Called after the user is signed out. #### onConnected Called when a socket connection is established. #### onDisconnected Called when the socket connection is dropped. Will be passed the auto-reconnect time (in milliseconds) as an argument. #### onSessionStarted Called when the connectifi agent has established a session to the service and directory. #### onSessionError Called when the connectifi agent can not establish a session to the service and directory. The error message will indicate which. This is a fatal error. #### onSessionAuthRequired Called when the user could not connect to the directory because they could not be authenticated. Passed the directory name as an argument. #### onAuthError Called when the user could not connect to the directory because they could not be authenticated. Passed the directory name as an argument. ## Handling Connection State Connectifi is designed to run anywhere including mobile devices where connection drop out is a common occurrence. The Agent provides a number of tools to mitigate potential connection issues. ### Auto-reconnect If a connection is dropped, the agent will automatically manage reconnecting. Toast in the default UI will also advise end users of connection state and any reconnect attempts. ### Connection Errors With FDC3 2.0, all FDC3 APIs are async. With Connectifi, any API call where a connection is rejected with an error of type `ConnectionError.NoConnectionAvailable`. This error can be handled and used to trigger a notification to the user, logging, and/or queueing of critical events. ### onConnect / onDisconnect callbacks These callbacks outlined above can be used to create custom UX around connection state. *NOTE:* these are not provided to manage reconnect logic as the agent itself takes care of that ## Security The agent provides full code isolation and its own secure connection back to its service endpoint. The directory an agent connects to controls the security profile of the interop. There are 3 basic security levels: - _Open_ - any app can connect to the end point - this is recommended for development only. - _App_ - an app's identity has to be validated and registered to the directory to connect. - _Strict_ - an app's identity must be valid and belong to the directory and the user must be authenticated to connect. This model allows for robust security where counterparties engaged in interop are not vulnerable to spoofing, cross-site-scripting attacks, and data leakage from unauthorized apps gaining access to a desktop bus. ## Troubleshooting Connectifi is fully of the web, so all the same tools and methods you'd use to troubleshoot a webapp apply for the agent. The logger callback can also provide more flexibility for inspecting and diagnosing any issues on the client. Some baseline items to check if you are having issues with the agent are: - is the URL to the interop service correct? - do you have the correct (case-sensitive) and fully qualified _appName@directoryName_ identifier for the directory you are connecting to? - is your app registered with the directory and does the registered origin match the origin you are connecting from? - are you using the [FDC3 2.0 API](https://fdc3.finos.org/docs/api/ref/DesktopAgent) in your app? ## Next Steps If you are not yet working with Connectifi, we'd love to hear from you. Please send us a message at [info@connectifi.co](mailto://info@connectifi.co). Next up on our roadmap for the Agent is: - Native client and language support - More improvements for the default UX - Greater configurability of the UX