Installing an Integration

Before your application can take advantage of a Fusebit IntegrationIntegration - An integration is the place the you write your code to get things done! Written in NodeJS, an integration runs in Fusebit's secure and scalable environment to translate between the needs of your backend application and the remote service or services you're connecting to., we need to enable a way for end-users to install the Integration. This ensures the user passes on the authentication and configuration settings the Integration needs in order to do it's job.

Fusebit provides an embeddable React-based Marketplace component that makes this easy.

Marketplace Component with 3 TilesMarketplace Component with 3 Tiles

Marketplace Component with 3 Tiles

The Fusebit Marketplace enables you to easily make all your IntegrationIntegration - An integration is the place the you write your code to get things done! Written in NodeJS, an integration runs in Fusebit's secure and scalable environment to translate between the needs of your backend application and the remote service or services you're connecting to.s available to users of your application through our beautiful React components that integrate directly with Fusebit.

The FusebitMarketplace component will create a tile for each Integration that you pass to it in an automatically generated gallery style page. Each Integration tile in this gallery will check directly with Fusebit to see if the TenantTenant - A single user of an integration, usually corresponding to a user or account in your own system. has already installed the Integration and reflect the state accordingly.

Getting Started

To get started, you can install it directly from npm:

npm i @fusebit/react-marketplace

Then, in your app, you can import the component directly :

import { Marketplace as FusebitMarketplace } from "@fusebit/react-marketplace";


const Marketplace = (props: {
  getIntegrations: integrationsList;
  onUninstall: (integrationId: string) => Promise<void>;
  getInstallUrl: (integrationId: string) => Promise<string>;
}) => {
  return (
        <FusebitMarketplace
              getIntegrations={props.getIntegrations}
          onUninstallClick={props.onUninstall}
          getInstallUrl={props.getInstallUrl}
        />
  );
}   

export default Marketplace;

The Marketplace Component requires an authenticated application to speak to Fusebit on it's behalf and retrieve information. The examples provided in this guide will assume the following general information flow:

🚧

Connecting your Application to Fusebit

If you haven't already connected your application backend to Fusebit, this is necessary to ensure that only authenticated systems/users have access to your integration data.

To get your application backend set up, read through Connecting Your Application.

Configuring the Component

The Marketplace Component requires three pieces of information to properly render a tile:

  • getIntegrations: What Integrations do you want to display in your marketplace gallery?
  • getInstallUrl: What is the redirect URL for each integration after installation has completed?
  • onUninstallClick: What is the flow for each integration after it has been uninstalled?

getIntegrations

An array consisting of integrations that will be displayed as a tile in your marketplace, this must include:

  • integrationId: Integration Name in Fusebit
  • feedId: The ID in the Fusebit Integrations Feed
  • isInstalled: Installation status of the specific integration for your tenant
  • title: User facing Integration name

In this example below, you are going to show two integrations (Asana & Github), and your tenant has already installed Asana but not Github.

integrationsList: [
   {
      "integrationId":"asana-integration-372",
      "feedId":"asana",
      "isInstalled":true,
      "title": "Asana Tasks Sync"
   },
   {
      "integrationId":"githubapp-integration-131",
      "feedId":"githubapp",
      "isInstalled":false,
      "title": "GitHub Issues Sync"
   }
]

Fusebit provides two separate endpoints to help you retrieve this information from your Fusebit Account using tenantId. In the example below, from your backend, we are querying all integrations in your Fusebit account and returning an integrationsList object with the required properties.

const getIntegrations = async (tenantId: string) => {

  const fusebitAuthHeaders = {
    Authorization: `Bearer ${fusebitJwt}`,
    'Content-Type': 'application/json; charset=utf-8',
  };

  // Returns a list of all integrations in your Fusebit  account
  // API Reference: https://developer.fusebit.io/reference/listintegrations
  const integrationsData = await fetch(`${fusebitBaseUrl}/integration`, {
    headers: fusebitAuthHeaders,
  });
  
  const integrations = await integrationsData.json();

  // Returns installation state of all integrations for the supplied tenantId
  // API Reference: https://developer.fusebit.io/reference/searchinstalls
  const installsResponse = await fetch(`${fusebitBaseUrl}/install?tag=fusebit.tenantId=${tenantId}`, {
    headers: fusebitAuthHeaders,
    });

  const installsData = await installsResponse.json();

  // Combine the results on integration.id and return one object
  const integrationsList = integrations.items.map((integration) => {
    const isInstalled = (installsData?.items || []).find((install: Install) => install.parentId === integration.id);

    return {
      integrationId: integration.id,
      feedId: integration.tags['fusebit.feedId'],
      isInstalled: !!isInstalled,
      title: integration.id,
    };
  });

  return integrationsList;
};

getInstallUrl

The Fusebit Marketplace Component handles the process of taking your users through the OAuth flow for your Integration and redirects them back to your app, this is done by creating a new SessionSession - A session is a sequence of steps that a user takes to configure a new integration, so that it can access their services on your behalf. You can think of a session as a combined task list and scratchpad - integrations will use a session to show forms and collect user data, and connectors will use a session to collect authentication credentials on behalf of the user. and polling it to check when and an InstallInstall - An install is a configured integration with a set of identity's and other values that represent a single user of the integration. You can search for instances by tags or by the special value of a Tenant (shh - it's also a tag, but don't tell anyone!). has successfully completed.

πŸ“˜

Fusebit provides the infrastructure required to authenticate your users, authorize your app, retrieve the proper OAuth access tokens and manage the token refresh process to ensure that your app always has access when it needs to run an integration.

For each integration, you need to:

  • Front-end: Provide the component with the route that it can trigger to start a new session and poll it
  • Back-end: Create a new session when the installation starts and redirect the user back when completed

In your front end, you would add this method:

const getInstallUrl = async (integrationId: string) => {
    // Route to the install endpoint on your backend
    const res = await fetch(`${appUrl}/${integrationId}/install`, {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        'Content-Type': 'application/json; charset=utf-8',
      },
    });
    const data = await res.json();
    return data.targetUrl;
  };

Next, in your backend, you will need to have two routes set up to manage the installation and handle any errors that may arise during the process.

  • Install: Create a new session and start listening
  • Callback: Commit the session and provide the post-installation redirect URL
router.get('/:integrationId/install', async (req, res, next) => {
    
  // Retrieve tenantId locally
  const tenantId: string = res.locals.data.tenantId;
  const appUrl: string = `${process.env.SSL_ENABLED ? 'https' : 'http'}://${req.headers.host}`;

  try {
    const integrationId: string = req.params.integrationId;
    const body = JSON.stringify({
      redirectUrl: `${appUrl}/${integrationId}/callback`,
      tags: {
        'fusebit.tenantId': tenantId.toString(),
      },
    });
    const headers = {
      Accept: 'application/json, text/plain, */*',
      'Content-Type': 'application/json',
      Authorization: `Bearer ${fusebitJWT}`,
    };

    // API Reference: https://developer.fusebit.io/reference/createsession
    const createSessionResponse = await fetch(`${fusebitBaseUrl}/integration/${integrationId}/session`, {
      body,
      headers,
      method: 'POST',
    });
    const session = await createSessionResponse.json();

    if (session.status > 299) {
      res.status(session.status);
      res.send({});
      return;
    }
    return res.send(session);
  } catch (e) {
    console.log('Error starting Fusebit session', e);
    res.sendStatus(500);
  }
});
router.get('/:integrationId/callback', async (req, res, next) => {
  const integrationId = req.params.integrationId;
  const appUrl: string = `${process.env.SSL_ENABLED ? 'https' : 'http'}://${req.headers.host}`;

  try {
    // Update this with your preferred data storage
    const sessionId = req.query.session;
    const sessionPersistResponse = await fetch(
      `${fusebitBaseUrl}/integration/${integrationId}/session/${sessionId}/commit`,
      {
        headers: {
          Accept: 'application/json, text/plain, */*',
          'Content-Type': 'application/json',
          Authorization: `Bearer ${fusebitJWT}`,
        },
        method: 'POST',
      }
    );

    if (sessionPersistResponse.status > 299) {
      throw 'ERROR: Fusebit session did not persist';
    }
    // Redirect URL to take your user after Install is complete
    res.redirect(`${appUrl}/marketplace`);
  } catch (e) {
    console.log('Error committing Fusebit session', e);
    res.sendStatus(500);
  }
});

πŸ“˜

Customize the Installation Flow

You can also add more screens and modify the installation flow as well. For instance, you may want to gather more information from your tenant. Read our guide on Collecting Configuration to implement this.

onUninstallClick

Similar to the installation process, Fusebit will also handle the process of removing an installation for you. All you need to do is find the installation and delete it.

const onUninstall = async (integrationName: string) => {
    await fetch(`/api/integration/${integrationName}/install`, {
      method: 'DELETE',
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        'Content-Type': 'application/json; charset=utf-8',
      },
      credentials: 'include',
    });
    getMe()
      .then((userData) => setUserData(userData))
      .catch(() => ({}));
  };
router.delete('/:integrationId/install', async (req, res) => {
  // Update this with your preferred data storage
  const tenantId: string = res.locals.data.getCurrentUserId();

  try {
    const integrationId: string = req.params.integrationId;
    // Get installation
    const lookupResponse = await fetch(
      `${fusebitBaseUrl}/integration/${integrationId}/install?tag=fusebit.tenantId=${tenantId}`,
      {
        headers: {
          Accept: 'application/json, text/plain, */*',
          'Content-Type': 'application/json',
          Authorization: `Bearer ${fusebitJwt}`,
        },
      }
    );
    const status = await lookupResponse.json();
    const installation = status.items?.[0];
    // Delete installation
    await fetch(`${fusebitBaseUrl}/integration/${integrationId}/install/${installation.id}`, {
      headers: {
        Accept: 'application/json, text/plain, */*',
        'Content-Type': 'application/json',
        Authorization: `Bearer ${fusebitJwt}`,
      },
      method: 'DELETE',
    });

    res.locals.data.clearTasks(currentUserId);

    res.sendStatus(200);
  } catch (e) {
    console.log('Error deleting Fusebit installation', e);
    res.sendStatus(500);
  }
});

Customization

This guide shows you the basics of using the Marketplace Component. However, as you embed the Component in your application, customization requirements will arise, including changing the styling to match your brand and your application's look-and-feel. Check out the Fusebit Marketplace Component document for all the customization options available out of the box.

Build your Own Installation Flow

If the Marketplace Component satisfies your requirements, you can skip this section.

In some rare cases, our Component may not be a fit and you may need to manually create the UI that installs an Integration. Fusebit provides a set of endpoints that then guide a user through an authorization and configuration flow, ensuring they grant the right permissions in the third-party system, and supply any parameters the integration needs to correctly install. After this flow, the user will be returned to your application.

Users in your system are modeled as TenantTenant - A single user of an integration, usually corresponding to a user or account in your own system.s in Fusebit, and the system creates one InstallInstall - An install is a configured integration with a set of identity's and other values that represent a single user of the integration. You can search for instances by tags or by the special value of a Tenant (shh - it's also a tag, but don't tell anyone!). per Tenant as the following flow is completed.

  1. The first step is to start a SessionSession - A session is a sequence of steps that a user takes to configure a new integration, so that it can access their services on your behalf. You can think of a session as a combined task list and scratchpad - integrations will use a session to show forms and collect user data, and connectors will use a session to collect authentication credentials on behalf of the user. in response to the user of your application choosing to install the Integration. The API documentation for this call is here. The accessKey and integrationBaseUrl properties come from the previous screen. The tenantId field would normally be the ID you use in your system to identify the given user, but in this case let's hard-code that to user2. Here we assume you want to bring the user back to the same page of your application when the installation is done, so we capture its address as the redirectUrl.
const tenantId = 'user2';
const response = await superagent.post(`${integrationBaseUrl}/session`)
  .set('Authorization', `Bearer ${accessKey}`)
  .send({
    redirectUrl: `${window.location.origin}${window.location.pathname}`,
        tags: {
        'fusebit.tenantId': tenantId
      }
    });
const sessionId = response.body.id;
  1. Now that a Session has been created, the rest of the steps here will use that as a transient "property bag". This next step starts the Integration installation flow by navigating the browser to the configuration endpoint of the Integration. The user will see a series of screens where they will be required to authorize access to the third-party system (Slack) and provide any configuration parameters needed.
const configureUrl = `${integrationBaseUrl}/session/${sessionId}/start`;
window.location.href = configureUrl;
  1. When the user's browser returns to the redirectUrl specified in the first step, we need to kick off the creation of the Integration Install:
let result = await superagent.post(`${integrationBaseUrl}/session/${sessionId}/commit`)
  .set('Authorization', `Bearer ${accessKey}`)
  .send();
const { targetUrl } = result.body;
  1. Creation of an Integration Install is an asynchronous process. We can immediately GET the targetUrl, which returns the created InstallInstall - An install is a configured integration with a set of identity's and other values that represent a single user of the integration. You can search for instances by tags or by the special value of a Tenant (shh - it's also a tag, but don't tell anyone!). object, to get the id and see if the operation has completed:
// Wait up to 10s for the creation of the integration install to complete

let install;

for (let n = 0; n < 10; n++) {
  result = await superagent.get(targetUrl)
    .set('Authorization', `Bearer ${accessKey}`);
  install = result.body;
  if (install.operationState.status != 'pending') {
    break;
  }
  await new Promise((resolve) => setTimeout(resolve, 1000));
};

// Print the install id
console.log(install.id);

Did this page help you?