Plugin Development

Getting Started

Plugins can extend the functionality of Ti2 and/or provide access to other platforms; you should use one of the following methods to provide a stadandarized way to access other systems; if you want to implement new functionality that is not encompassed on the available methods you are more than welcome to add new methods and contribute to the Ti2 codebase.

Steps to develop a plugin

  1. Contact us to get access to the ti2- repository.
    • We will create a skeleton repository for you and add you as a contributor.
  2. Clone the repository and start developing your plugin.
  3. Follow respective tutorials to develop your plugin.
  4. Make sure to add tests for your plugin. For more information, refer to the Testing Suite section.
  5. Contact us to deploy your plugin to our hosted staging instance.
    • Please let us know any environment variables that are required for your plugin to work. For more information, refer to the Environment variables section.
    • We will provide you with tokens and endpoints to test your plugin.
  6. Test your plugin against our staging instance.
  7. Contact us once you have tested your plugin and it's working as expected.
    • We will deploy your plugin to our production instance.

Available Methods

We are using a set of standardized methods to maintain compatibility; the following methods are available, more can be supported but should be a added to the TI2 spec first to maximize it's compatibility.

General Methods (should always be included):

Content Methods (marketing information for (company)profile, locations and/or products):

Booking Methods (bookings and operations):


Environment variables

Plugin environment keys are passed down from the hosting ti2 instance when they are generated, this is the preferred way to access environment keys from the plugins, such values are not design to hold client / user's API keys or specific data, they are to be stored in the database via the AppKey collection.

Plugin ENV variable name convention: ti2_pluginName_environmenVariableName, for example:

ti2_tourconnect_apiUrl=http://backend:8080

Codebase setup

##Skip this section if you are using the skeleton repository.##

You can review some of the plugins previously developed and use them as a guide, plugin methods are encourage to include tests and to return results with the same format; however, the returned values can include additional information, currently Ti2 is expected to run on node version 12.22.8; we suggest you use the same for your codebase.

After initializing your git environment for development; you can initialize your node project; we recommend naming your repo ti2-<pluginname> but it is not required.

$ node init .

Entry file / constructor

##Skip this section if you are using the skeleton repository.##

The plugin is expected to have an index.js file that exports a Plugin Class like so:

// index.js
class Plugin {
  constructor(params = {}) { // we get the env variables from here
    Object.entries(params).forEach(([attr, value]) => {
      this[attr] = value;
    });
  }
}
module.exports = Plugin;

The constructor of the plugins should extend the attributes with the received params, this allows the plugin to receive configurations or settings that are to be used by any user, for example, settings data that can be shared across accounts.

By default Ti2 will pass down all environment variables that match the plugin name to it's constructor; for example, if the following environment variables are defined:

ti2_ventrata_acceptLanguage=en
ti2_travelgate_clientCode=tourconnect
ti2_tourconnect_apiUrl=http://backend:8080

The plugin named ti2-ventrata would receive acceptLanguage variable and it's value, ti2-travelgate the clientCode variable and ti-touconnect the apiUrl value.

Method calling

##Skip this section if you are using the skeleton repository.##

On the previous example code we are declaring a validateToken method; we are normally expected to receive two parameters one is token and the second one payload.

// index.js
class Plugin {
  constructor(params = {}) { // we get the env variables from here
    Object.entries(params).forEach(([attr, value]) => {
      this[attr] = value;
    });
  }
  async validateToken({
    token: {
      apiKey = this.apiKey,
      apiUrl = this.apiUrl,
    },
  }) {
   // TODO: actually test the apiKey against the integration.
    return assert(apiKey);
  }
}
module.exports = Plugin;

The token parameter should store all the settings for the current configured end user; on these example we are defaulting this settings to the ones configured to the pluging when it was instanced; so if the user settings do not include an apiUrl it will fall back to the environment variable on the running server.

Testing Suite

The plugin should contain a test file, for the following example assumes we will be using jest as the testing platform.

$ npm i -D jest
// index.test.js
const Plugin = require('./index');

const app = new Plugin({
  jwtKey: process.env.ti2_ventrata_jwtKey,
});

describe('Base Tests', () => {
  const token = { // a valid token to run tests
    apiKey: process.env.ti2_ventrata_apiKey,
    endpoint: process.env.ti2_ventrata_endpoint,
  };
  it('should not validate an invalid api key', async () => {
    const isValid = await app.validateToken({
    token: { apiKey: 'some Randomg Text' }
    }); 
    expect(isValid).toBeFalsy();
  });
  it('should not validate an invalid api key', async () => {
    const isValid = await app.validateToken({
      token,
    }); 
    expect(isValid).toBeTruthy();
  });
});

$ npx jest

Deploying

To your own Instance

After setting up a Ti2 instance you can add your development folder as a package on your instance's npm repo (not on the plugin's instance), like so:

  "dependencies": {
    "ti2-myplugin": "file:../ti2-myplugin"
    }

To our hosted instance

Contact us with the npm published package to begin the testing and deployment process.

Extending the base API

##Typically not needed##

Ti2 uses the Swagger API specification standard to define it's own methods, you can review the basic methods here; these methods can be extended using the same format; this allows any plugin to extend the base API endpoints that are linked to any plugin method (part of the basic methods or new ones).

All the extender methods would be availble under the /[plugin name] namespace, i.e.: /ti2-greatPlugin/ping.

For Ti2 to pickup the extender definition, one must create a new file on the root of the plugin named api.yml, such file would contain only the methods to be extended and it must comply with the current swagger version used on ti2.

# api.yml
openapi: '3.0.0'
info:
  description: My amazing ti2-plugin
  version: 0.0.1 
  title: Ti2's new plugin
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    ServerInfo:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
        version:
          type: string
        uptime:
          type: string
paths:
  /ping:
    get:
      tags:
        - public
      summary: Should return basic system status
      operationId: ping
      responses:
        '200':
          description: success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerInfo'

Database Migrations (plugin's own database tables)

##Typically not needed##

Ti2 uses Sequelize v6.13 which is the database ORM we ecourage you to use, you can add your own tables via migrations that should be placed under a migrations folder on the root of the plugin folder.

Migrations should be run after the fact, from the root of ti2 instance like so:

$ ti2 dbapp [plugin name] migrate

We strongly encourage all the plugin's table names are prefixed with the plugin name (i.e. ti2-pluginName-cacheTable).

This should be executed after all ti2 host migrations had been executed.

$ ti2 db migrate

Scheduled Task (to be released on v2)

TBD.