Welcome to OpenFin Docs

Explore our guides, resources and references for building with OpenFin

Get Started

Channel API

Channel API

OpenFin’s Channel API on the InterApplicationBus (IAB) provides optionality for secure desktop messaging between your application and other OpenFin applications. These approaches enable an end-user to send/receive data or events across applications without the need to leave the desktop or rekey in information.

The OpenFin Channel API permits communication between OpenFin applications via an asynchronous request-response messaging channel. It offers asynchronous methods for discovery, transfer of data, and lifecycle management, allowing applications to complete events outside of the main process flow while permitting multiple actions at the same time. There is only one owner or channelProvider per channel, but multiple clients or channelClient can connect to a channel. A channel creates a pathway between the channelClient and channelProvider for messages to be dispatched bidirectionally.

Why use the Channel API

The Channels API is recommended for developers who want to employ asynchronous calls into their workflow, employ two-way, secure communication between themselves and clients, or provide a service on OpenFin. By convention, OpenFin-developed system apps use the Channels API to provide an API to other applications running on the desktop. This is an ideal case for where one wants to expose their own client-side API. The Provider creates a channel which clients can then connect to and send messages across.

Provider

A channel can be created with a unique channel name by calling Channel.create. The create method returns a promise that resolves to an instance of the channelProvider bus. The caller then becomes the “channel provider” and can use the channelProvider bus to register actions and middleware. The caller can also set an onConnection and/or onDisconnection listener that executes on any new channel connection/disconnection attempt from a channel client.

To reject a connection, simply throw an error in the onConnection listener. A map of client connections is updated automatically on client connection and disconnection and saved in the [read-only] connections property on the channelProvider bus. The channel exists until the provider destroys it or disconnects by closing or destroying the context (navigating or reloading).

Setup

async function makeProvider() {
   // entity creates a channel and becomes the channelProvider
   const providerBus = await fin.InterApplicationBus.Channel.create('channelName');
  
   providerBus.onConnection((identity, payload) => {
       // can reject a connection here by throwing an error
       console.log('Client connection request identity: ', JSON.stringify(identity));
       console.log('Client connection request payload: ', JSON.stringify(payload));
   });

   providerBus.register('topic', (payload, identity) => {
       // register a callback for a 'topic' to which clients can dispatch an action
       console.log('Action dispatched by client: ', JSON.stringify(identity));
       console.log('Payload sent in dispatch: ', JSON.stringify(payload));
   });
}
static async void MakeProvider()
        {
            var fin = GetRuntime();

            var providerBus = fin.InterApplicationBus.Channel.CreateProvider("channelName");
            
            providerBus.Opened += (identity, payload) =>
            {
                Console.WriteLine("Client connection request identity: ", identity?.ToString());
                Console.WriteLine("Client connection request payload: ", payload?.ToString());
            };
            
            providerBus.RegisterTopic<Object>("topic", (payload, client) =>
            {
                Console.WriteLine("Action dispatched by client: ", client.RemoteEndpoint);
                Console.WriteLine("Payload sent in dispatch: ", payload?.ToString());
            });
        }

Client

A connection can be made to a channel as a channelClient by calling Channel.connect with a given channel name. The connection request is routed to the channelProvider. The connect call returns a promise that will resolve with a channelClient bus if accepted by the channelProvider, or reject if the channelProvider throws an error to reject the connection. This bus can communicate with the Provider, but not to other clients on the channel. Using the bus, the channelClient can register actions and middleware. Channel lifecycle can also be handled with an onDisconnection listener.

📘

NOTE

If the connection request is sent prior to creation, the promise will not resolve or reject until the channel is created by a channelProvider (whether or not to wait for creation is configurable in the connectOptions).

Setup

async function makeClient(channelName) {
   // A payload can be sent along with channel connection requests to help with authentication
   const connectPayload = { payload: 'token' };

   // If the channel has been created this request will be sent to the provider.  If not, the
   // promise will not be resolved or rejected until the channel has been created. 
   const clientBus = await fin.InterApplicationBus.Channel.connect('channelName', connectPayload);

   clientBus.onDisconnection(channelInfo => {
       // handle the channel lifecycle here - we can connect again which will return a promise
       // that will resolve if/when the channel is re-created.
       makeClient(channelInfo.channelName);
   })

   clientBus.register('topic', (payload, identity) => {
       // register a callback for a topic to which the channel provider can dispatch an action
       console.log('Action dispatched by provider: ', JSON.stringify(identity));
       console.log('Payload sent in dispatch: ', JSON.stringify(payload));
       return {
           echo: payload
       };
   });
}
static async void MakeClient()
        {
            var fin = GetRuntime();

            var opts = new ChannelConnectOptions("channelName")
            {
                // A payload can be sent along with channel connection requests to help with authentication
                Payload = "token"
            };

            var clientBus = fin.InterApplicationBus.Channel.CreateClient(opts);
            // If the channel has been created this request will be sent to the provider.  If not, the
            // promise will not be resolved or rejected until the channel has been created.
            await clientBus.ConnectAsync();


            clientBus.Closed += (identity, payload) =>
            {
                // handle the channel lifecycle here - we could attempt re-open a closed channel
                // or clean up after it, or else write to logs
                Console.WriteLine("Channel closed by: ", identity);
                Console.WriteLine("Channel closed payload: ", payload);
            };
            
            // register a callback for a topic to which the channel provider can dispatch an action
            clientBus.RegisterTopic<Object>("topic", (payload) =>
            {
                Console.WriteLine("Payload sent in dispatch: ", payload);
            });
        }

Messaging

Messages can be dispatched from the channelProvider to any channelClient or from any channelClient to the channelProvider. The use case below demonstrates the usage of a channel, in which a message is dispatched from a client to the provider for a specific topic and a corresponding acknowledgement is returned to the client:

  1. Message dispatched over the channel for a specific topic to the corresponding provider
  2. Provider callback for that registered action is invoked
  3. Return value of the registered action is sent from the provider as a payload to the acknowledgement (ack) response or the error message to a negative-acknowledgement (nack)
  4. Ack or nack is routed back to the client and the promise resolves with this payload

Example

// previously created channelBus here could be a client or provider
   channelBus.register('topic1', (payload, identity) => {
       // register a callback for a given topic to which the sender can dispatch an action
       const somePayload = someAction(payload, identity);
       // The return value will be returned as a payload in response to the sender
       return somePayload;
   });

   channelBus.setDefaultAction((topic, payload, senderIdentity) => {
       // register a callback for topics that do not have a registered action
       return 'Dispatch will now resolve with this payload instead of reject on sender side.';
   });

   // if channelBus is a providerBus, there are three arguments to dispatch
   // and the first argument determines which client to target.
   const response = await channelBus.dispatch('topic2', 'payload');

   // the dispatch method returns a promise that resolves to the return value of the
   // register method's listener of the channel counterpart. 
   document.querySelector('#someId').innerText = response;
}
static async void MakeClient()
        {
            var fin = GetRuntime();

            var opts = new ChannelConnectOptions("channelName")
            {
                // A payload can be sent along with channel connection requests to help with authentication
                Payload = "token"
            };

            var clientBus = fin.InterApplicationBus.Channel.CreateClient(opts);
            // If the channel has been created this request will be sent to the provider.  If not, the
            // promise will not be resolved or rejected until the channel has been created.
            await clientBus.ConnectAsync();


            clientBus.Closed += (identity, payload) =>
            {
                // handle the channel lifecycle here - we could attempt re-open a closed channel
                // or clean up after it, or else write to logs
                Console.WriteLine("Channel closed by: ", identity);
                Console.WriteLine("Channel closed payload: ", payload);
            };
            
            // register a callback for a topic to which the channel provider can dispatch an action
            clientBus.RegisterTopic<Object>("topic", (payload) =>
            {
                Console.WriteLine("Payload sent in dispatch: ", payload);
            });
        }

Each time a provider receives an incoming dispatch, it is required to send back a corresponding acknowledgement (can be undefined). Once a listener is registered for a particular action, it stays in place receiving and responding to incoming messages until it is removed. This messaging mechanism works exactly the same when messages are dispatched from the provider to a client. However, the provider has an additional publish method that sends messages to all connected clients.
If a message is dispatched to an action that does not have a callback registered, an error is returned. This default error can be overwritten on the channelBus by setting a default action.

Updated 12 days ago


Channel API


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.