# Develop a Connector

The [flowmate.io](http://flowmate.io) platform supports `Node.js` programming language for building connectors.

To help you create a component in `Node.js` we have created a simple `My SaaS connector` which connects to the described below API and demonstrates multiple features of the platform.

## My SaaS Rest API

To help you create a component in Node.js, we have prepared a simple MySaaS component that connects to the MySaaS API and demonstrates various features of the platform.

### MySaaS Component

Let's take a look at the structure of the MySaaS Component in Node.js:

```scss
mysaas-component-nodejs
├── component.json                                          (1)
├── lib
│   ├── actions                                             (2)
│   │   ├── createContact.js
│   ├── lookups                                             (3)
│   │   ├── lookup.js
│   ├── schemas                                             (4)
│   │   ├── createContact.in.json
│   │   ├── createContact.out.json
│   │   └── getAllContacts.in.json
│   │   └── getAllContacts.out.json
│   └── triggers                                            (5)
│       ├── getAllContacts.js
├── package.json                                            (6)
```

The [Flowmate.io](http://Flowmate.io) platform utilizes Node.js components built using the `NPM run-script`. This script first examines the `package.json` (6) configuration file to set up the node and npm versions and then initiates the build process. It subsequently downloads and constructs all necessary dependencies. For all Node.js components, the following dependency is mandatory:

```json
"dependencies": {
      "@openintegrationhub/ferryman": "2.4.4",
}
```

\*Ferryman version 2.4.4 is the most current at the time of writing the documentation. Please always use the latest version.

Ferryman serves as the Node.js SDK for the [Flowmate.io](http://Flowmate.io) platform, facilitating component integration by providing a straightforward programming model and ensuring effective communication with the platform.

The `component.json` file (1) acts as the descriptor for the component, used by the platform to collect all necessary information for displaying it in the user interface (UI). This descriptor exclusively lists the component’s functionality, including triggers and actions.

The `lib` directory contains subdirectories `actions`(2), `lookups`(3), `schemas`(4), and `triggers`(5), as defined in the `component.json` file. The source files for Node.js are located in `lib/actions` and `lib/triggers`, while the JSON schemas that define the component’s metadata are stored in `lib/schemas`.

### Component Descriptor

The `component.json` file acts as the component descriptor, which the platform uses to gather all necessary information about the component. Let's examine the descriptor for the MySaaS component:

```json
{
  "title": "My SaaS Connector",                                            (1)
  "description": "Connector for the MySaaS API",                           (2)
  "triggers": {                                                            (3)
    ...
  },
  "actions": {                                                             (4)
    ...
  }
}

```

In this descriptor, the component title (1) and description (2) are specified.

The [`triggers`](https://www.notion.so/How-to-develop-connector-without-openapi-spec-ba67c08e544340bea544f23b37268a07?pvs=21) (3) and [`actions`](https://www.notion.so/How-to-develop-connector-without-openapi-spec-ba67c08e544340bea544f23b37268a07?pvs=21) (4) properties outline the component's available triggers and actions.

### Triggers

Let's delve into how triggers are defined. The example below illustrates the triggers section from the `component.json` descriptor file:

```json
"triggers": {
	"lookup": {                                                         (1)
      "main": "./lib/lookups/lookup.js",
      "title": "Function to run a lookup",
      "description": "Function to call an internal trigger"
    },
  "getAllContacts": {                                                 (2)
    "main": "./lib/triggers/getAllContacts.js",                       (3)
    "title": "Get All Contacts",                                      (4)
    "metadata": {                                                     (5)
	    "in": "./lib/schemas/getAllContacts.in.json"                    (6)
      "out": "./lib/schemas/getAllContacts.out.json"                  (7)
    }
  }
}

```

In this description `lookup` (1) trigger describes file for calling internal trigger in fields with `Lookup selector` type.

In this example, the trigger identified by `getallContacts` (2) is executed by the function specified in `getAllContacts.js` (3). Metadata (5) described input params and schema of response. Input metadata contains path to json schema with input parameters and is defined in the `getAllContacts.in.json` file(6). Output metadata is defined in the `getAllContacts.out.json` file (7).

#### Lookup trigger

Such kind of trigger gathers all other triggers and execute if on `lookup selector field` .

This is what it looks like on the platform:

When you open mapping setup page, choose `Lookup selector` field type and specify other required fields

<figure><img src="/files/eQiEbDItWR7HTAJntOKa" alt=""><figcaption></figcaption></figure>

In almost all cases you can use the standard code:

```jsx
const logger = require("@openintegrationhub/ferryman/lib/logging");

async function processAction(req, res, _, actionParams) {
  const { secretId, data } = actionParams;
  const { ferryman } = req;
  const { operationId, parameters, cfg } = data;
  const { process: triggerProcess } = require(`../triggers/${operationId}`);

  const msg = { data: parameters || {}, metadata: {} };

  const snapshot = {},
    incomingMessageHeaders = {};
  const tokenData = { function: operationId };

  // only when the secretId parameter is provided
  if (secretId) {
    const { authorization } = req.headers;
    const splittedAuthorization = authorization.split(" ");
    const token = splittedAuthorization[1];

    try {
      const secret = await ferryman.fetchSecret(secretId, token);
      Object.assign(cfg, secret);
    } catch (error) {
      logger.error("Error getting the secret", error);
    }
  }

  // to not fail the lookup function when they call to the emit function
  globalThis.emit = () => {};
  const context = {
    logger,
    emit() {},
  };
  const dataResponse = await triggerProcess.call(
    context,
    msg,
    cfg,
    snapshot,
    incomingMessageHeaders,
    tokenData
  );

  return res.send(dataResponse);
}

module.exports.process = processAction;
```

### Trigger `getAllContacts`

Trigger function `process` has 3 arguments:

```jsx
async function process(msg, cfg, snapshot = {}) {...}
```

#### Context

Function `process` called with context which contain 2 main options: `logger` and `emit`

you can access logger like:

```jsx
  this.logger.info('Get All Contacts trigger started'); 
```

This logger based on [`bunyan`](https://www.npmjs.com/package/bunyan) logger and contains different levels like `info`, `debug`, `trace` , etc.

Emitter `emit` allows execute next operation:

emit data:

```jsx
      this.emit('data', { data: {...}, metadata: {...} });
```

data object must contain `data` and `metadat` objects.

emit snapshot (see above).

#### Argument `msg`

It is incoming message. This data you can set on `Mapping` section on platform UI:

<figure><img src="/files/gOETxi51h2DPm8A0Zo6d" alt=""><figcaption></figcaption></figure>

When you push `pensil` button you see fields from object which described at input metadata schema, for `getAllContacts` trigger it is /lib/schemas/getAllContacts.in.json.

All specified data stored in `msg` object like:

```jsx
{ data: { name: 'Hello' } }
```

#### Argument `cfg`

It is incoming configuration. At `Options` section you see the same fields like in `Mapping` . This data stored in `cfg` object like:

```jsx
{ triggerParams: { name: 'Hello' } }
```

But `cfg` object also contains service information. In `nodeSettings` object you can find following options:

* `skipSnapshot` - if this options is `true` - trigger should return array of first found items. This options is true when trigger used in `Lookup selector`
* `continueOnError` - if this option enabled, and during trigger execution caused error - trigger should emit empty message like:

```jsx
this.emit('data', { data: {}, metadata: {} });
```

In the UI this is checkbox:

<figure><img src="/files/X0FShcJyOisEf5T4GNv8" alt=""><figcaption></figcaption></figure>

* `initialSnapshot` - if this option enabled, snapshot will contain date when flow created. This option prevent initial import

In the UI it is checkbox:

<figure><img src="/files/NGd4zmv0frauEBot2gNc" alt=""><figcaption></figcaption></figure>

#### Argument `snapshot`

It is object where we need to store information between flow execution. The most common case - save here lastUpdated timestamp and on the next execution filter items which we already emited on previous executions. If API contains pagination functionality it is possible to save next token in snapshot.

When we retrieve items and emit them, it is needed to emit new version of snapshot like:

```jsx
    this.emit('snapshot', {lastUpdated: '2024-07-03T12:57:36.216Z'});
```

### Actions

Let's delve into how actions are defined. The example below illustrates the actions section from the `component.json` descriptor file:

```json
"actions": {
  "createContact": {                                                 (1)
    "main": "./lib/triggers/createContact.js",                       (2)
    "title": "Create Contact",                                       (3)
    "metadata": {                                                    (4)
	    "in": "./lib/schemas/createContact.in.json"                    (5)
      "out": "./lib/schemas/createContact.out.json"                  (6)
    }
  }
}

```

In this example, the action identified by `createContact` (1) is executed by the function specified in `createContact.js` (2). Title for this action described at (3). Metadata (4) described input params and schema of response. Input metadata contains path to json schema with input parameters and is defined in the `createContact.in.json` file(5). Output metadata is defined in the `createContact.out.json` file (6).

Actions has the same context and incoming arguments, but `snapshot` argument in most cases is unused.

### 🔐 Authorization Types

This page documents the four supported authorization types. The actual credentials are stored in the `cfg` field. You can use these values to authorize API requests in your integration code.

***

#### 1. `API_KEY`

**Structure:**

```json
{
  "cfg": {
    "key": "API_KEY_HERE",
    "headerName": "X-API-Key"
  }
}

```

If `headerName` is specified, use it to set a custom header:

`{ [headerName]: key }`

If `headerName` is **not** provided (empty), use the default:

`Authorization: key`

**Example usage:**

```jsx
const axios = require('axios');

class Client {
  constructor(context, cfg, baseURL) {
    this.logger = context.logger;
    this.cfg = cfg;
    this.baseURL = baseURL;
  }

  async apiRequest(options, axiosInstance = axios) {
    const headers = {
      ...(options.headers || {})
    };

    if (this.cfg.headerName) {
      headers[this.cfg.headerName] = this.cfg.key;
    } else {
      headers['Authorization'] = this.cfg.key;
    }

    const callOptions = {
      ...options,
      baseURL: this.baseURL,
      headers
    };

    try {
      return await axiosInstance.request(callOptions);
    } catch (err) {
      this.logger.error(`API request failed: ${err.message}`);
      throw err;
    }
  }
}

module.exports = Client;
```

***

#### 2. `OA2_AUTHORIZATION_CODE`

**Structure:**

```json
{
  "cfg": {
    "authClientId": "xxx",
    "refreshToken": "xxx",
    "accessToken": "xxx",
    "expires": "ISO_DATE",
    "externalId": "user-id"
  }
}
```

**Example usage:**

```jsx
const axios = require('axios');

class Client {
  constructor(context, cfg, baseURL) {
    this.logger = context.logger;
    this.cfg = cfg;
    this.baseURL = baseURL;
  }

  async apiRequest(options, axiosInstance = axios) {
    const callOptions = {
      ...options,
      baseURL: this.baseURL,
      headers: {
        ...(options.headers || {}),
        Authorization: `Bearer ${this.cfg.accessToken}`
      }
    };

    try {
      return await axiosInstance.request(callOptions);
    } catch (err) {
      this.logger.error(`API request failed: ${err.message}`);
      throw err;
    }
  }
}

module.exports = Client;
```

***

#### 3. `SIMPLE` (Username & Password)

**Structure:**

```json
{
  "cfg": {
    "username": "user@example.com",
    "passphrase": "password123"
  }
}
```

**Example usage:**

```jsx
const axios = require('axios');

class Client {
  constructor(context, cfg, baseURL) {
    this.logger = context.logger;
    this.cfg = cfg;
    this.baseURL = baseURL;
  }

  async apiRequest(options, axiosInstance = axios) {
    const auth = {
      username: this.cfg.username,
      password: this.cfg.passphrase
    };

    const callOptions = {
      ...options,
      baseURL: this.baseURL,
      auth
    };

    try {
      return await axiosInstance.request(callOptions);
    } catch (err) {
      this.logger.error(`API request failed: ${err.message}`);
      throw err;
    }
  }
}

module.exports = Client;

```

***

#### 4. `SESSION_AUTH`

**Structure:**

```json
{
  "cfg": {
    "authClientId": "xxx",
    "accessToken": "xxx",
    "inputFields": {
      "username": "user@example.com",
      "password": "password123"
    },
    "expires": null}
}

```

This method assumes a session-based authentication, where credentials are used to obtain an access token. The token is reused until expiration.

**Example usage:**

```jsx
const axios = require('axios');

class Client {
  constructor(context, cfg, baseURL) {
    this.logger = context.logger;
    this.cfg = cfg;
    this.baseURL = baseURL;
  }

  async apiRequest(options, axiosInstance = axios) {
    const callOptions = {
      ...options,
      baseURL: this.baseURL,
      headers: {
        ...(options.headers || {}),
        Authorization: `Bearer ${this.cfg.accessToken}`
      }
    };

    try {
      return await axiosInstance.request(callOptions);
    } catch (err) {
      this.logger.error(`API request failed: ${err.message}`);
      throw err;
    }
  }
}

module.exports = Client
```

### Summary

You can see example of connector [here](https://github.com/CloudEcosystemDev/My-SaaS-Connector).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://flowmate.gitbook.io/flowmate-documentation/connectors/develop-your-connector/develop-a-connector.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
