diff --git a/types/openfin-fdc3/index.d.ts b/types/openfin-fdc3/index.d.ts new file mode 100644 index 00000000000000..7a0085471f55f7 --- /dev/null +++ b/types/openfin-fdc3/index.d.ts @@ -0,0 +1,53 @@ +// Type definitions for openfin-fdc3 0.2 +// Project: https://github.com/HadoukenIO/fdc3-service#readme +// Definitions by: bryangaleOF +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +// Minimum TypeScript Version: 3.0 + +/** + * Overview + * When running within the OpenFin Runtime, and the `fdc3Api` flag in your manifest is set, your web applications will + * have access to the "fdc3" namespace without the need to include additional source files. You can treat the "fdc3" + * namespace as you would the "window", "navigator" or "document" objects. This is an alternative to using the + * "openfin-fdc3" NPM package. + */ +declare namespace fdc3 { + type AppChannel = import('./internal/main').AppChannel; + type AppDirIntent = import('./internal/main').AppDirIntent; + type AppId = import('./internal/main').AppId; + type AppImage = import('./internal/main').AppImage; + type AppIntent = import('./internal/main').AppIntent; + type AppName = import('./internal/main').AppName; + type Application = import('./internal/main').Application; + type ApplicationError = import('./internal/main').ApplicationError; + type Channel = import('./internal/main').Channel; + type ChannelBase = import('./internal/main').ChannelBase; + type ChannelChangedEvent = import('./internal/main').ChannelChangedEvent; + type ChannelContextListener = import('./internal/main').ChannelContextListener; + type ChannelError = import('./internal/main').ChannelError; + type ChannelId = import('./internal/main').ChannelId; + type ChannelWindowAddedEvent = import('./internal/main').ChannelWindowAddedEvent; + type ChannelWindowRemovedEvent = import('./internal/main').ChannelWindowRemovedEvent; + type ConnectionError = import('./internal/main').ConnectionError; + type ContactContext = import('./internal/main').ContactContext; + type Context = import('./internal/main').Context; + type ContextListener = import('./internal/main').ContextListener; + type DefaultChannel = import('./internal/main').DefaultChannel; + type DisplayMetadata = import('./internal/main').DisplayMetadata; + type FDC3Error = import('./internal/main').FDC3Error; + type Icon = import('./internal/main').Icon; + type InstrumentContext = import('./internal/main').InstrumentContext; + type IntentListener = import('./internal/main').IntentListener; + type IntentMetadata = import('./internal/main').IntentMetadata; + type IntentResolution = import('./internal/main').IntentResolution; + type Intents = import('./internal/main').Intents; + type Listener = import('./internal/main').Listener; + type NameValuePair = import('./internal/main').NameValuePair; + type OrganizationContext = import('./internal/main').OrganizationContext; + type ResolveError = import('./internal/main').ResolveError; + type SendContextError = import('./internal/main').SendContextError; + type SystemChannel = import('./internal/main').SystemChannel; +} + +declare const fdc3: typeof import('./internal/main'); diff --git a/types/openfin-fdc3/internal/context.d.ts b/types/openfin-fdc3/internal/context.d.ts new file mode 100644 index 00000000000000..19e659ad7092e2 --- /dev/null +++ b/types/openfin-fdc3/internal/context.d.ts @@ -0,0 +1,116 @@ +/** + * TypeScript definitions for context objects. + * + * These structures are defined by the Contexts FDC3 working group. This contains the Context interface for you to create your own + * contexts, as well as a set of standard contexts agreed by the FDC3 working group. + */ +/** + * General-purpose context type, as defined by [FDC3](https://fdc3.finos.org/docs/1.0/context-intro). + * A context object is a well-understood datum that is streamable between FDC3 participants. As a result + * it has a field describing what type it is, and data indicating its identity. Use this as a base + * to derive your own with any custom properties or metadata. + */ +export interface Context { + /** + * The type of the context that uniquely identifies it, e.g. "fdc3.instrument". + * This is used to refer to the accepted context(s) when declaring intents. See [[AppDirIntent]]. + */ + type: string; + /** + * The name of the context data (optional). This is a text string that describes the data being sent. + * Implementors of context may choose to make the name mandatory. + */ + name?: string; + /** + * An optional map of any equivalent identifiers for the context type, e.g. ISIN, CUSIP, etc. for an instrument. + */ + id?: { + [key: string]: string | undefined; + }; +} +/** + * Built-in context to define a contact. + */ +export interface ContactContext extends Context { + /** + * The context type is always 'fdc3.contact'. + */ + type: 'fdc3.contact'; + /** + * Free text name of the contact. + */ + name: string; + /** + * The contact data. Can contain some or all of: + * * `email`: Email address + * * `twitter`: Twitter handle + * * `phone`: Phone number + */ + id: { + [key: string]: string; + } & { + email?: string; + twitter?: string; + phone?: string; + }; +} +/** + * Built-in context to define a financial instrument. + */ +export interface InstrumentContext extends Context { + /** + * The context type is always 'fdc3.instrument'. + */ + type: 'fdc3.instrument'; + /** + * Optional free text name of the instrument. + */ + name?: string; + /** + * The instrument data. Can contain some or all of: + * * `ticker`: a ticker + * * `ISIN`: [ISIN](https://www.isin.org/isin/) + * * `CUSIP`: [CUSIP](https://www.cusip.com/cusip/index.htm) + * * `SEDOL`: [SEDOL](https://www.londonstockexchange.com/products-and-services/reference-data/sedol-master-file/sedol-master-file.htm) + * * `RIC`: [Reuters Instrument Code (RIC)](https://en.wikipedia.org/wiki/Reuters_Instrument_Code) + * * `BBG`: [Bloomberg Ticker](https://www.bloomberg.com/professional/product/market-data/) + * * `PERMID`: [PERMID](https://permid.org/) + * * `FIGI`: [FIGI](https://www.openfigi.com/about/figi) + */ + id: { + [key: string]: string; + } & { + ticker?: string; + ISIN?: string; + CUSIP?: string; + SEDOL?: string; + RIC?: string; + BBG?: string; + PERMID?: string; + FIGI?: string; + }; +} +/** + * Built-in context to define an organization. + */ +export interface OrganizationContext extends Context { + /** + * The context type is always fdc3.organization. + */ + type: 'fdc3.organization'; + /** + * Optional free text name of the organization. + */ + name?: string; + /** + * The organization data. Can contain either or both + * * `LEI`: [LEI](https://www.gleif.org/en/about-lei/introducing-the-legal-entity-identifier-lei) + * * `PERMID`: [PERMID](https://permid.org/) + */ + id: { + [key: string]: string; + } & { + LEI?: string; + PERMID?: string; + }; +} diff --git a/types/openfin-fdc3/internal/contextChannels.d.ts b/types/openfin-fdc3/internal/contextChannels.d.ts new file mode 100644 index 00000000000000..4a114eb01f97af --- /dev/null +++ b/types/openfin-fdc3/internal/contextChannels.d.ts @@ -0,0 +1,329 @@ +/** + * Context channels allow end-user filtering of context broadcasts. Each window is assigned to a particular + * context channel, and any [[broadcast|broadcasts]] by any window in that channel will only be recevied by the other + * windows in that channel. The assignment of windows to channels would typically be managed by the user, through + * either a channel selector widget built into the window itself, or through a separate channel manager application. + * + * All windows will initially be placed in the [[defaultChannel|default channel]], and will remain there unless they + * explicitly [[join]] another channel. + * + * There are three types of channels: [[DefaultChannel]], [[SystemChannel]] and [[AppChannel]]. + */ +import { Identity } from 'openfin/_v2/main'; +import { ChannelTransport, SystemChannelTransport, AppChannelTransport } from './internal'; +import { Context } from './context'; +import { ContextListener } from './main'; +/** + * Type used to identify specific Channels. Though simply an alias of `string`, use of this type indicates use of the string + * as a channel identifier, and that the user should avoid assuming any internal structure and instead treat as a fully opaque object + */ +export type ChannelId = string; +/** + * Defines the suggested visual appearance of a system channel when presented in an app, for example, as part of a channel selector. + */ +export interface DisplayMetadata { + /** + * A user-readable name for this channel, e.g. `"Red"` + */ + name: string; + /** + * The color that should be associated with this channel when displaying this channel in a UI, e.g. `#FF0000`. + */ + color: string; + /** + * A URL of an image that can be used to display this channel + */ + glyph: string; +} +/** + * Union of all possible concrete channel classes that may be returned by the service. + */ +export type Channel = DefaultChannel | SystemChannel | AppChannel; +/** + * Event fired when a window is added to a channel. See {@link ChannelBase.addEventListener}. + * + * Note that this event will typically fire as part of a pair - since windows must always belong to a channel, a window + * can only join a channel by leaving its previous channel. The exceptions to this rule are when the window is created + * and destroyed when there will be no previous channel or no current channel, respectively. + * + * To listen for channel changes across all (or multiple) channels, there is also a top-level {@link ChannelChangedEvent}. + * + * @event + */ +export interface ChannelWindowAddedEvent { + type: 'window-added'; + /** + * The window that has just been added to the channel. + */ + identity: Identity; + /** + * The channel that window now belongs to. Will always be the channel object that {@link ChannelBase.addEventListener} was + * called on. + */ + channel: Channel; + /** + * The channel that the window belonged to previously. + * + * Will be `null` if this event is being fired on a newly-created window. + */ + previousChannel: Channel | null; +} +/** + * Event fired when a window is removed from a channel. See {@link ChannelBase.addEventListener}. + * + * Note that this event will typically fire as part of a pair - since windows must always belong to a channel, a window + * can only join a channel by leaving it's previous channel. The exceptions to this rule are when the window is created + * and destroyed when there will be no previous channel or no current channel, respectively. + * + * To listen for channel changes across all (or multiple) channels, there is also a top-level {@link ChannelChangedEvent}. + * + * @event + */ +export interface ChannelWindowRemovedEvent { + type: 'window-removed'; + /** + * The window that has just been removed from the channel. + */ + identity: Identity; + /** + * The channel that the window now belongs to. + * + * Will be `null` if the window is leaving the channel due to it being closed. + */ + channel: Channel | null; + /** + * The channel that the window belonged to previously. Will always be the channel object that {@link ChannelBase.addEventListener} was + * called on. + */ + previousChannel: Channel; +} +/** + * Event fired whenever a window changes channel. See {@link addEventListener}. + * + * This event can be used to track all channel changes, rather than listening only to a specific channel. + * + * See also {@link ChannelWindowAddedEvent}/{@link ChannelWindowRemovedEvent} + * + * @event + */ +export interface ChannelChangedEvent { + type: 'channel-changed'; + /** + * The window that has switched channel. + */ + identity: Identity; + /** + * The channel that the window now belongs to. + * + * Will be `null` if the window has just been closed, and so is being removed from a channel without being added to + * another. + */ + channel: Channel | null; + /** + * The previous channel that the window belonged to. + * + * Will be `null` if the window has just been created, and so doesn't have a previous channel. + */ + previousChannel: Channel | null; +} +/** + * Listener for context broadcasts coming from a specific channel. Generated by {@link ChannelBase.addContextListener}. + */ +export interface ChannelContextListener extends ContextListener { + /** + * The channel that this listener is observing. + * + * Listener will trigger whenever a context is broadcast on this channel. + */ + channel: Channel; +} +/** + * Class representing a context channel. All interactions with a context channel happen through the methods on here. + * + * When users wish to generically handle both {@link SystemChannel}s, {@link AppChannel}s and the + * {@link DefaultChannel}, generally the {@link Channel} type should be used instead of {@link ChannelBase}. + */ +export abstract class ChannelBase { + /** + * Constant that uniquely identifies this channel. Will be generated by the service, and guaranteed to be unique + * within the set of channels registered with the service. + * + * In the case of `system` channels (see {@link SystemChannel}), these IDs _should_ persist across sessions. The + * channel list is defined by the service, but can be overridden by a desktop owner. If the desktop owner keeps + * this list static (which is recommended), then IDs will also persist across sessions. + */ + readonly id: ChannelId; + /** + * Uniquely defines each channel type. + * + * See overrides of this class for list of allowed values. + */ + readonly type: string; + protected constructor(id: string, type: string); + /** + * Returns a list of all windows belonging to the specified channel. + * + * If the window making the call is a member of this channel, it will be included in the results. If there are no + * windows on this channel, an empty array is returned. + */ + getMembers(): Promise; + /** + * Returns the last context that was broadcast on this channel. All channels initially have no context, until a + * window is added to the channel and then broadcasts. If there is not yet any context on the channel, this method + * will return `null`. The context is also reset back into its initial context-less state whenever a channel is + * cleared of all windows. + * + * The context of a channel will be captured regardless of how the context is broadcasted on this channel - whether + * using the top-level FDC3 `broadcast` function, or using the channel-level {@link broadcast} function on this + * object. + * + * NOTE: Only non-default channels are stateful, for the default channel this method will always return `null`. + */ + getCurrentContext(): Promise; + /** + * Adds the given window to this channel. If no identity is provided, the window making the call will be the window + * added to the channel. + * + * If the channel has a current context (see {@link getCurrentContext}) then that context will be immediately passed to + * the given window upon joining the channel, via its context listener(s). + * + * Note that all windows will always belong to exactly one channel at all times. If you wish to leave a channel, + * the only way to do so is to join another channel. A window may rejoin the default channel by calling `channels.defaultChannel.join()`. + * + * @param identity The window that should be added to this channel. If omitted, will use the window that calls this method. + * @throws If `identity` is passed, [[FDC3Error]] with an [[ConnectionError]] code. + * @throws If `identity` is passed, `TypeError` if `identity` is not a valid + * {@link https://developer.openfin.co/docs/javascript/stable/global.html#Identity | Identity}. + */ + join(identity?: Identity): Promise; + /** + * Broadcasts the given context on this channel. + * + * Note that this function can be used without first joining the channel, allowing applications to broadcast on + * channels that they aren't a member of. + * + * This broadcast will be received by all windows that are members of this channel, *except* for the window that + * makes the broadcast. This matches the behavior of the top-level FDC3 `broadcast` function. + * + * @param context The context to broadcast to all windows on this channel. + * @throws `TypeError` if `context` is not a valid [[Context]]. + */ + broadcast(context: Context): Promise; + /** + * Event that is fired whenever a window broadcasts on this channel. + * + * This can be triggered by a window belonging to the channel calling the top-level FDC3 `broadcast` function, or by + * any window calling this channel's {@link broadcast} method. + * + * @param handler Function that should be called whenever a context is broadcast on this channel. + */ + addContextListener(handler: (context: Context) => void): ChannelContextListener; + /** + * Event that is fired whenever a window joins this channel. This includes switching to/from the default + * channel. + * + * The event also includes which channel the window was in previously. The `channel` property within the + * event will always be this channel instance. + */ + addEventListener(eventType: 'window-added', handler: (event: ChannelWindowAddedEvent) => void): void; + /** + * Event that is fired whenever a window leaves this channel. This includes switching to/from the default + * channel. + * + * The event also includes which channel the window is being added to. The `previousChannel` property within the + * event will always be this channel instance. + */ + addEventListener(eventType: 'window-removed', handler: (event: ChannelWindowRemovedEvent) => void): void; + removeEventListener(eventType: 'window-added', handler: (event: ChannelWindowAddedEvent) => void): void; + removeEventListener(eventType: 'window-removed', handler: (event: ChannelWindowRemovedEvent) => void): void; +} +/** + * The channel all windows start in. + * + * Unlike system channels, the default channel has no pre-defined name or visual style. It is up to apps to display + * this in the channel selector as they see fit - it could be as "default", or "none", or by "leaving" a user channel. + * + * An instance of the default channel is available from the [[defaultChannel]] getter API. + */ +export class DefaultChannel extends ChannelBase { + readonly type: 'default'; +} +/** + * User-facing channels, to display within a color picker or channel selector component. + * + * This list of channels should be considered fixed by applications - the service will own the list of user channels, + * making the same list of channels available to all applications, and this list will not change over the lifecycle of + * the service. + * + * To fetch the list of available channels, use [[getSystemChannels]]. + */ +export class SystemChannel extends ChannelBase { + readonly type: 'system'; + /** + * How a client application should present this channel in any UI. + */ + readonly visualIdentity: DisplayMetadata; +} +/** + * Custom application-created channels. + * + * Applications can create these for specialised use-cases. These channels should be obtained by name by calling + * {@link getOrCreateAppChannel} and it is up to your organization to decide how applications are aware of this name. + * As with organization defined contexts, app channel names should have a prefix specific to your organization to avoid + * name collisions, e.g. `'company-name.channel-name'`. + * + * App channels can be joined by any window, but are only indirectly discoverable if the name is not known. + */ +export class AppChannel extends ChannelBase { + readonly type: 'app'; + /** + * The name of this channel. This is the same string as is passed to [[getOrCreateAppChannel]]. + */ + readonly name: string; +} +/** + * The channel in which all windows will initially be placed. + * + * All windows will belong to exactly one channel at all times. If they have not explicitly been placed into a channel + * via a {@link ChannelBase.join} call, they will be in this channel. + * + * If an app wishes to leave any other channel, it can do so by (re-)joining this channel. + */ +export const defaultChannel: DefaultChannel; +/** + * Gets all service-defined system channels. + * + * This is the list of channels that should be used to populate a channel selector. All channels returned will have + * additional metadata that can be used to populate a selector UI with a consistent cross-app channel list. + */ +export function getSystemChannels(): Promise; +/** + * Fetches a channel object for a given channel identifier. The `channelId` property maps to the {@link ChannelBase.id} field. + * + * @param channelId The ID of the channel to return + * @throws [[FDC3Error]] with an [[ChannelError]] code. + */ +export function getChannelById(channelId: ChannelId): Promise; +/** + * Returns the channel that the current window is assigned to. + * + * @param identity The window to query. If omitted, will use the window that calls this method. + * @throws If `identity` is passed, [[FDC3Error]] with an [[ConnectionError]] code. + * @throws If `identity` is passed, `TypeError` if `identity` is not a valid + * {@link https://developer.openfin.co/docs/javascript/stable/global.html#Identity | Identity}. + */ +export function getCurrentChannel(identity?: Identity): Promise; +/** + * Returns an app channel with the given name. Either creates a new channel or returns an existing channel. + * + * It is up to your organization to decide how to share knowledge of these custom channels. As with organization + * defined contexts, app channel names should have a prefix specific to your organization to avoid name collisions, + * e.g. `'company-name.channel-name'`. + * + * The service will assign a unique ID when creating a new app channel, but no particular mapping of name to ID should + * be assumed. + * + * @param name The name of the channel. Must not be an empty string. + * @throws `TypeError` if `name` is not a valid app channel name, i.e., a non-empty string. + */ +export function getOrCreateAppChannel(name: string): Promise; diff --git a/types/openfin-fdc3/internal/directory.d.ts b/types/openfin-fdc3/internal/directory.d.ts new file mode 100644 index 00000000000000..e290bea11ac20d --- /dev/null +++ b/types/openfin-fdc3/internal/directory.d.ts @@ -0,0 +1,159 @@ +/** + * TypeScript definitions for objects returned by the Application Directory. + * + * These structures are defined by the App-Directory FDC3 working group. The definitions here are based on the 1.0.0 + * specification which can be found [here](https://fdc3.finos.org/appd-spec). + */ +/** + * Type alias to indicate when an Application Identifier should be passed. Application Identifiers + * are described [here](https://fdc3.finos.org/docs/1.0/appd-discovery#application-identifier). + * + * In the OpenFin implementation of FDC3, we expect this to be the same as the + * [UUID in the manifest](https://developers.openfin.co/docs/application-configuration), but this can be configured + * using [[Application.customConfig]]. + * + * This type alias exists to disambiguate the raw string app identity from the [[AppName]]. + */ +export type AppId = string; +/** + * App Name is the machine-readable name of the app, but it may well be sufficiently + * human-readable that it can be used in user interfaces. If it's not, please use the title. + * + * This type alias exists to disambiguate the raw string app name from the [[AppId]]. + */ +export type AppName = string; +/** + * An application in the app directory. + */ +export interface Application { + /** + * The Application Identifier. Please see https://fdc3.finos.org/docs/1.0/appd-discovery#application-identifier. + * By convention this should be the same as your [OpenFin UUID](https://developers.openfin.co/docs/application-configuration). + * + * If you can't use your OpenFin UUID as the appId, then instead specify your application's UUID by adding an + * `appUuid` property to the [[customConfig]] field. + */ + appId: AppId; + /** + * The machine-readable app name, used to identify the application in various API calls to the application directory. + * This may well be human-readable, too. If it's not, you can provide a title, and that will be used everywhere + * a name needs to be rendered to a user. + */ + name: AppName; + /** + * An application manifest, used to launch the app. This should be a URL that points to an OpenFin JSON manifest. + */ + manifest: string; + /** + * The manifest type. Always `'openfin'`. + */ + manifestType: string; + /** + * The version of the app. Please use [semantic versioning](https://semver.org/). + */ + version?: string; + /** + * The human-readable title of the app, typically used by the launcher UI. If not provided, [[name]] is used. + */ + title?: string; + /** + * A short explanatory text string. For use in tooltips shown by any UIs that display app information. + */ + tooltip?: string; + /** + * Longer description of the app. + */ + description?: string; + /** + * Images that can be displayed as part of the app directory entry. Use these for screenshots, previews or similar. These are not the + * application icons - use [[icons]] for that. + */ + images?: AppImage[]; + /** + * Contact email address. + */ + contactEmail?: string; + /** + * The email address to send your support requests to. + */ + supportEmail?: string; + /** + * Name of the publishing company, organization, or individual. + */ + publisher?: string; + /** + * Icons used in the app directory display. A launcher may be able to use various sizes. + */ + icons?: Icon[]; + /** + * Additional config. + * + * The OpenFin FDC3 service supports the following configuration values: + * * `appUuid`: Informs the service that the application launched by this [[manifest]] will have this UUID. By + * default, the service will expect the UUID of the application to match the [[appId]]. This configuration value + * can be used to override this. + * + * Any additional fields will still be accessible to applications (via APIs such as [[findIntent]]), but will not + * have any impact on the operation of the service. + */ + customConfig?: NameValuePair[]; + /** + * The set of intents associated with this application directory entry. + */ + intents?: AppDirIntent[]; +} +/** + * An image for an app in the app directory. + */ +export interface AppImage { + /** + * A URL that points to an image. + */ + url: string; + /** + * Alt text to be displayed with the image. + */ + tooltip?: string; + /** + * Additional text description. + */ + description?: string; +} +/** + * An icon for an app in the app directory. + */ +export interface Icon { + /** + * A URL that points to an icon. + */ + icon: string; +} +/** + * A pair of name and values, that allows extra configuration to be passed in to an application. + */ +export interface NameValuePair { + name: string; + value: string; +} +/** + * A representation of an [FDC3 Intent](https://fdc3.finos.org/docs/1.0/intents-intro) supported by the app in the app directory. + */ +export interface AppDirIntent { + /** + * The intent name. + */ + name: string; + /** + * A short, human-readable description of this intent. + */ + displayName?: string; + /** + * The context types that this intent supports. A context type is a namespaced name; + * examples are given [here](https://fdc3.finos.org/docs/1.0/context-spec). + */ + contexts?: string[]; + /** + * Custom configuration for the intent. Currently unused, reserved for future use. + */ + customConfig?: any; +} diff --git a/types/openfin-fdc3/internal/errors.d.ts b/types/openfin-fdc3/internal/errors.d.ts new file mode 100644 index 00000000000000..b8dd383d492450 --- /dev/null +++ b/types/openfin-fdc3/internal/errors.d.ts @@ -0,0 +1,97 @@ +/** + * Errors related to launching or interacting with a particular application. + */ +export enum ApplicationError { + /** + * Indicates that an application of the provided name could not be found, either running or in the application directory. + */ + NotFound = "ApplicationError:NotFound", + /** + * Indicates that an application could not be started from an OpenFin manifest. + */ + LaunchError = "ApplicationError:LaunchError", + /** + * Indicates that a timeout was reached before the application was started. + */ + LaunchTimeout = "ApplicationError:LaunchTimeout" +} +/** + * Error codes relating to the context channel system. + */ +export enum ChannelError { + /** + * Indicates that a channel of a provided ID does not exist. + */ + ChannelWithIdDoesNotExist = "ChannelError:ChannelWithIdDoesNotExist" +} +/** + * Error codes relating to connections to the FDC3 service, from OpenFin windows or otherwise. + */ +export enum ConnectionError { + /** + * Indicates that no window with a provided OpenFin Identity is registered with the FDC3 service. + */ + WindowWithIdentityNotFound = "ConnectionError:WindowWithIdentityNotFound" +} +/** + * Errors related to resolving an application to handle an intent and context. + */ +export enum ResolveError { + /** + * Indicates that no application could be found to handle the provided intent and context. + */ + NoAppsFound = "ResolveError:NoAppsFound", + /** + * Indicates that a provided application does not handle the provided intent and context. + */ + AppDoesNotHandleIntent = "ResolveError:AppDoesNotHandleIntent", + /** + * Indicates that intent resolution has been cancelled because the user dismissed the intent resolver UI. + */ + ResolverClosedOrCancelled = "ResolveError:ResolverClosedOrCancelled" +} +/** + * Errors related to sending a context, possibly as part of an intent, to another application registered with the FDC3 service + */ +export enum SendContextError { + /** + * Indicates that the target application has no windows that have a relevant handler for the given context. + */ + NoHandler = "SendContextError:NoHandler", + /** + * Indicates that all handlers for the given context threw an error when invoked. + */ + HandlerError = "SendContextError:HandlerError", + /** + * Indicates that all handers for the given context failed to completed before a timeout was reached + */ + HandlerTimeout = "SendContextError:SendIntentTimeout" +} +/** + * Class used to hold errors returned by the FDC3 provider. Inherits from the built-in + * [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) type. + * + * Note that not all errors raised by the service will be of type `FDC3Error`. Standard JavaScript error types such as + * `TypeError` and `Error` can also be thrown by the API. + */ +export class FDC3Error extends Error { + /** + * A string from one of [[ApplicationError]], [[ChannelError]], [[ConnectionError]], [[ResolveError]] or [[SendContextError]]. + * + * Future versions of the service may add additional error codes. Applications should allow for the possibility of + * error codes that do not exist in the above enumerations. + */ + code: string; + /** + * Always `'FDC3Error'`. + */ + name: string; + /** + * Description of the error that occurred. + * + * These messages are not intended to be user-friendly, we do not advise displaying them to end users. If + * error-specific user messaging is required, use [[code]] to determine what message should be displayed. + */ + message: string; + constructor(code: string, message: string); +} diff --git a/types/openfin-fdc3/internal/intents.d.ts b/types/openfin-fdc3/internal/intents.d.ts new file mode 100644 index 00000000000000..b97fefd7a5795f --- /dev/null +++ b/types/openfin-fdc3/internal/intents.d.ts @@ -0,0 +1,17 @@ +/** + * Enum that defines the standard set of intents that are defined as part of the FDC3 specification. + * + * This enum exists only as a helper, applications may define their own set of constants if they prefer. + */ +export enum Intents { + DIAL_CALL = "DialCall", + SAVE_CONTACT = "SaveContact", + SAVE_INSTRUMENT = "SaveInstrument", + SHARE_CONTEXT = "ShareContext", + START_CALL = "StartCall", + START_CHAT = "StartChat", + VIEW_CONTACT = "ViewContact", + VIEW_CHART = "ViewChart", + VIEW_QUOTE = "ViewQuote", + VIEW_NEWS = "ViewNews" +} diff --git a/types/openfin-fdc3/internal/internal.d.ts b/types/openfin-fdc3/internal/internal.d.ts new file mode 100644 index 00000000000000..e9db9c58aed799 --- /dev/null +++ b/types/openfin-fdc3/internal/internal.d.ts @@ -0,0 +1,20 @@ +/** + * File contains types and helpers used to communicate between client and provider. + * + * These exports are a part of the client, but are not required by applications wishing to interact with the service. + * This file is excluded from the public-facing TypeScript documentation. + */ +import { ChannelId, DefaultChannel, SystemChannel, DisplayMetadata, ChannelBase, AppChannel } from './contextChannels'; + +export interface ChannelTransport { + id: ChannelId; + type: string; +} +export interface SystemChannelTransport extends ChannelTransport { + type: 'system'; + visualIdentity: DisplayMetadata; +} +export interface AppChannelTransport extends ChannelTransport { + type: 'app'; + name: string; +} diff --git a/types/openfin-fdc3/internal/main.d.ts b/types/openfin-fdc3/internal/main.d.ts new file mode 100644 index 00000000000000..b816c78ea9a767 --- /dev/null +++ b/types/openfin-fdc3/internal/main.d.ts @@ -0,0 +1,281 @@ +import { Context } from './context'; +import { Application, AppName } from './directory'; +import { ChannelChangedEvent, ChannelContextListener } from './contextChannels'; +/** + * This file was copied from the FDC3 v1 specification. + * + * Original file: https://github.com/FDC3/FDC3/blob/master/src/api/interface.ts + */ +export * from './contextChannels'; +export * from './context'; +export * from './directory'; +export * from './intents'; +export * from './errors'; + +/** + * Describes an intent. + */ +export interface IntentMetadata { + /** + * The machine readable name of the intent. + */ + name: string; + /** + * The human-readable name of the intent. + */ + displayName: string; +} +/** + * An interface that relates an intent to apps. This is returned by [[findIntent]] and [[findIntentsByContext]], which gives + * you a set of apps that can execute a particular intent. + */ +export interface AppIntent { + /** + * Descriptor of this intent. + */ + intent: IntentMetadata; + /** + * An array of applications that are associated with this intent. + */ + apps: Application[]; +} +/** + * Provides a standard format for data returned upon resolving an intent. + * + * ```javascript + * // You might fire and forget an intent + * await agent.raiseIntent("intentName", context); + * + * // Or you might want some data to come back + * const result = await agent.raiseIntent("intentName", context); + * const data = result.data; + * ``` + */ +export interface IntentResolution { + /** + * The machine-readable name of the app that resolved this intent. + */ + source: AppName; + /** + * Any data returned by the target application's intent listener. + * + * If the target application registered multiple listeners, this will be the first non-`undefined` value returned + * by a listener. + */ + data?: unknown; + /** + * For future use. Right now always the string `'1.0.0'`. + */ + version: string; +} +/** + * Listener type alias, generic type that can be used to refer any context or intent listener. + */ +export type Listener = ContextListener | IntentListener | ChannelContextListener; +/** + * Listener for context broadcasts. Generated by [[addContextListener]]. + */ +export interface ContextListener { + /** + * The handler for when this listener receives a context broadcast. + */ + handler: (context: Context) => void; + /** + * Unsubscribe the listener object. We will no longer receive context messages on this handler. + * + * Calling this method has no effect if the listener has already been unsubscribed. To re-subscribe, call + * [[addContextListener]] again to create a new listener object. + */ + unsubscribe: () => void; +} +/** + * Listener for intent sending. Generated by [[addIntentListener]]. + */ +export interface IntentListener { + /** + * The intent name that we are listening to. Is whatever is passed into [[addIntentListener]]. + */ + intent: string; + /** + * The handler for when this listener receives an intent. + */ + handler: (context: Context) => unknown | Promise; + /** + * Unsubscribe the listener object. We will no longer receive intent messages on this handler. + * + * Calling this method has no effect if the listener has already been unsubscribed. To re-subscribe, call + * [[addIntentListener]] again to create a new listener object. + */ + unsubscribe: () => void; +} +/** + * A desktop agent is a desktop component (or aggregate of components) that serves as a + * launcher and message router (broker) for applications in its domain. + * + * A desktop agent can be connected to one or more App Directories and will use directories for application + * identity and discovery. Typically, a desktop agent will contain the proprietary logic of + * a given platform, handling functionality like explicit application interop workflows where + * security, consistency, and implementation requirements are proprietary. + */ +/** + * Launches/links to an app by name. The application will be started if it is not already running. + * + * If a [[Context]] object is passed in, this object will be provided to the opened application via a [[ContextListener]]. + * + * If opening errors, it returns an [[FDC3Error]] with a string from the [[ApplicationError]] export enumeration. + * + * ```javascript + * // No context + * agent.open('myApp'); + * // With context + * agent.open('myApp', context); + * ``` + * @param name The [[AppName]] to launch. + * @param context A context to pass to the app post-launch. + * @throws [[FDC3Error]] with an [[ApplicationError]] code. + * @throws If `context` is passed, [[FDC3Error]] with a [[SendContextError]] code. + * @throws If `context` is passed, `TypeError` if `context` is not a valid [[Context]]. + */ +export function open(name: AppName, context?: Context): Promise; +/** + * Find out more information about a particular intent by passing its name, and optionally its context. + * + * `findIntent` is effectively granting programmatic access to the desktop agent's resolver. + * A promise resolving to the intent, its metadata and metadata about the apps registered to handle it is returned. + * This can be used to raise the intent against a specific app. + * + * For example, I know `'StartChat'` exists as a concept, and want to know more about it. + * ```javascript + * const appIntent = await agent.findIntent("StartChat"); + * ``` + * + * This returns a single [[AppIntent]] (some fields omitted for brevity, see [[Application]] for full list of `apps` fields): + * ```ts + * { + * intent: { name: "StartChat", displayName: "Chat" }, + * apps: [{ name: "Skype" }, { name: "Symphony" }, { name: "Slack" }] + * } + * ``` + * + * We can then raise the intent against a particular app + * ```javascript + * await agent.raiseIntent(appIntent.intent.name, context, appIntent.apps[0].name); + * ``` + * @param intent The intent name to find. + * @param context An optional context to send to find the intent. + * @throws If `context` is passed, an [[FDC3Error]] with a [[SendContextError]] code. + * @throws If `context` is passed, `TypeError` if `context` is not a valid [[Context]]. + */ +export function findIntent(intent: string, context?: Context): Promise; +/** + * Find all the available intents for a particular context. + * + * `findIntentsByContext` is effectively granting programmatic access to the desktop agent's resolver. + * A promise resolving to all the intents, their metadata and metadata about the apps registered to handle it is + * returned, based on the context export types the intents have registered. + * + * An empty array will be returned if there are no available intents for the given context. + * + * For example, I have a context object and I want to know what I can do with it, so I look for intents... + * ```javascript + * const appIntents = await agent.findIntentsByContext(context); + * ``` + * This returns an array of [[AppIntent]] objects such as the following (some fields omitted for brevity, see + * [[Application]] for full list of `apps` fields): + * ```ts + * [ + * { + * intent: { name: "StartCall", displayName: "Call" }, + * apps: [{ name: "Skype" }] + * }, + * { + * intent: { name: "StartChat", displayName: "Chat" }, + * apps: [{ name: "Skype" }, { name: "Symphony" }, { name: "Slack" }] + * } + * ] + * ``` + * + * We could now use this by taking one of the intents, and raising it. + * ```javascript + * // Select a particular intent to raise + * const selectedIntent = appIntents[1]; + * + * // Raise the intent, passing the given context, letting the user select which app to use + * await agent.raiseIntent(selectedIntent.intent.name, context); + * + * // Raise the intent, passing the given context, targeting a particular app + * const selectedApp = selectedIntent.apps[0]; + * await agent.raiseIntent(selectedIntent.intent.name, context, selectedApp.name); + * ``` + * @param context Returned intents must support this context. + * @throws [[FDC3Error]] with a [[SendContextError]] code. + * @throws `TypeError` if `context` is not a valid [[Context]]. + */ +export function findIntentsByContext(context: Context): Promise; +/** + * Publishes context to other apps on the desktop. Any apps using [[addContextListener]] will receive this. + * ```javascript + * agent.broadcast(context); + * ``` + * + * Only windows in the same [[ChannelBase|channel]] as the broadcasting window will receive the context. All windows + * will initially be in the same channel (referred to as the [[defaultChannel|default channel]]). See + * [[ContextChannels]] for more details. + * + * Note that windows do not receive their own broadcasts. If the window calling `broadcast` has also added one or more + * [[addContextListener|context listeners]], then those listeners will not fire as a result of this broadcast. + * + * @param context The context to broadcast. + * @throws `TypeError` if `context` is not a valid [[Context]]. + */ +export function broadcast(context: Context): Promise; +/** + * Raises an intent to the desktop agent to resolve. Intents can be either targeted or non-targeted, determined by the + * presence or absense of the `target` argument. For non-targeted intents, the service will search the directory and + * any running applications to find an application that can handle the given intent and context. If there are multiple + * such applications, the end user will be asked to select which application they wish to use. + * + * If the application isn't already running, it will be started by the service. The intent data will then be passed to + * the target application's intent listener. The promise returned by this function resolves when the service has + * confirmed that the target application has been started its intent listener has completed successfully. + * + * The returned [[IntentResolution]] object indicates which application handled the intent (if the intent is a targeted + * intent, this will always be the value passed as `target`), and contains the data returned by the target applications + * intent listener (if any). + * + * ```javascript + * // Raise an intent to start a chat with a given contact + * const intentR = await agent.raiseIntent("StartChat", context); + * // Use the IntentResolution object to target the same chat app with a new context + * agent.raiseIntent("StartChat", newContext, intentR.source); + * ``` + * @param intent The intent name to raise. + * @param context The context that will be sent with this intent. + * @param target An optional [[AppName]] to send the intent to. + * @throws [[FDC3Error]] with a [[ResolveError]] code. + * @throws [[FDC3Error]] with an [[ApplicationError]] code. + * @throws [[FDC3Error]] with a [[SendContextError]] code. + * @throws `TypeError` if `context` is not a valid [[Context]]. + */ +export function raiseIntent(intent: string, context: Context, target?: AppName): Promise; +/** + * Adds a listener for incoming intents from the Agent. + * + * To unsubscribe, use the returned [[IntentListener]]. + * @param intent The name of the intent to listen for. + * @param handler The handler to call when we get sent an intent. + */ +// tslint:disable-next-line no-any-union +export function addIntentListener(intent: string, handler: (context: Context) => any | Promise): IntentListener; +/** + * Adds a listener for incoming context broadcasts from the desktop agent. + * + * To unsubscribe, use the returned [[ContextListener]]. + * @param handler The handler function to call when we receive a broadcast context. + */ +export function addContextListener(handler: (context: Context) => void): ContextListener; +/** + * Event that is fired whenever a window changes from one channel to another. This captures events from all channels (including the default channel). + */ +export function addEventListener(eventType: 'channel-changed', handler: (event: ChannelChangedEvent) => void): void; +export function removeEventListener(eventType: 'channel-changed', handler: (event: ChannelChangedEvent) => void): void; diff --git a/types/openfin-fdc3/openfin-fdc3-tests.ts b/types/openfin-fdc3/openfin-fdc3-tests.ts new file mode 100644 index 00000000000000..9a5a61b0b15e09 --- /dev/null +++ b/types/openfin-fdc3/openfin-fdc3-tests.ts @@ -0,0 +1,37 @@ +async function test(): Promise { + const contextListener: fdc3.ContextListener = fdc3.addContextListener((context: fdc3.Context) => {}); + contextListener.unsubscribe(); + + fdc3.addEventListener('channel-changed', (event: fdc3.ChannelChangedEvent) => {}); + + const intentListener: fdc3.IntentListener = fdc3.addIntentListener('test-intent', (context: fdc3.Context) => {}); + intentListener.unsubscribe(); + + await fdc3.broadcast({type: 'test-context'}); + + const appIntent: fdc3.AppIntent = await fdc3.findIntent('test-intent'); + + const appIntents: fdc3.AppIntent[] = await fdc3.findIntentsByContext({type: 'test-context'}); + + const channelById: fdc3.Channel = await fdc3.getChannelById('test-channel-id'); + await channelById.join(); + + const currentChannel: fdc3.Channel = await fdc3.getCurrentChannel(); + await currentChannel.join(); + + const appChannel: fdc3.Channel = await fdc3.getOrCreateAppChannel('test-app-channel-name'); + await appChannel.join(); + + const systemChannels: fdc3.Channel[] = await fdc3.getSystemChannels(); + + await fdc3.open('test-app'); + await fdc3.open('test-app', {type: 'test-context'}); + + await fdc3.raiseIntent('test-intent', {type: 'test-context'}); + await fdc3.raiseIntent('test-intent', {type: 'test-context'}, 'test-target'); + + fdc3.removeEventListener('channel-changed', (event: fdc3.ChannelChangedEvent) => {}); + + await fdc3.defaultChannel.join(); + await fdc3.defaultChannel.broadcast({type: 'test-context'}); +} diff --git a/types/openfin-fdc3/tsconfig.json b/types/openfin-fdc3/tsconfig.json new file mode 100644 index 00000000000000..171f5d2e64cbcb --- /dev/null +++ b/types/openfin-fdc3/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "openfin-fdc3-tests.ts" + ] +} diff --git a/types/openfin-fdc3/tslint.json b/types/openfin-fdc3/tslint.json new file mode 100644 index 00000000000000..4e880718523608 --- /dev/null +++ b/types/openfin-fdc3/tslint.json @@ -0,0 +1 @@ +{"extends": "dtslint/dt.json"}