Hull Connectors

Connectors (previously called “Ships”) are the preferred way for developers to integrate external services with Hull.

Introduction

Connectors make it easy for anyone to build and publish reusable connectors. Connectors are standalone, framework-agnostic, externally hosted apps that interact with Hull APIs.

They can have client and server components. They take care of embedding client code, managing settings and lifecycle and data flow.

Server-side connectors have realtime read-write access to Hull. They do not require any local storage and are stateless by default. They subscribe to Hull, receive data in realtime along with all the needed credentials to act upon it and write data back to hull. Our open source connectors show how this can be used to integrate with any third-party app.

Client-side connectors can be embedded in any website where hull.js is installed from a simple Point-n-click interface.

You build a Ship by describing the the information needed to run your application and the settings exposed to your users to configure it in a file called manifest.json at the root of your project.

It is then used by Hull to automatically build the admin section in Hull’s Dashboard, expose settings, fetch and embed client-side code, and send events to server-side code.

Our Github Organization has dozens of examples of open-source Connectors

File Structure

Connectors can have any combination of Server-side code and Client-side code, or both.

Server-side

The minimal setup for a server-side connector:

  • A package.json setting up the npm start command to launch a web server.
  • A manifest.json file, with a subscriptions entry defining an HTTP endpoint where Hull will send events.
minimal-connector
├── server.js
├── package.json
└── manifest.json

manifest.json

{
  "name" : "minimal-connector",
  "version" : "0.1.0",
  "subscriptions" : [ { "url" : "/notify" } ]
}

package.json

{
  ...
  "dependencies": {
    "express": "^4.13.3",
    "hull": "^0.5.4"
  },
  "scripts": {
    "start": "node server.js"
  }
}

server.js

import express from 'express';
import { NotifHandler, BatchHandler, Routes, Middleware } from 'hull';
const { Readme, Manifest } = Routes;

const app = express();
app.get("/manifest.json", Manifest(__dirname));
app.get("/", Readme);
app.get("/readme", Readme);

app.use(hull.currentUserMiddleware);

app.post('/notify', NotifHandler({
  hostSecret: "1234",
  groupTraits: true,
  onError: (message, status) => console.warn("Error", status, message),
  handlers: {
    "ship:update":    (notif, context) => context.hull.logger.warn(user),
    "segment:update": (notif, context) => context.hull.logger.warn(user),
    "segment:delete": (notif, context) => context.hull.logger.warn(user),
    "user:delete":    (notif, context) => context.hull.logger.warn(user),
    "user:create":    (notif, context) => context.hull.logger.warn(user),
    "user:update":    (notif, context) => context.hull.logger.warn(user),
    "event":          (notif, context) => context.hull.logger.warn(user),
  }
}));

app.post("/batch", BatchHandler({
  hostSecret: "1234",
  groupTraits: true,
  batchSize: 100,
  handler: (notifications = [], { hull, ship }) => {
    hull.logger.debug("batch.process", { notifications: notifications.length });
    notifications.map((notif) => updateUser(notif, {
      hull,
      ship,
      isBatch: true
    }));
  }
}));
/*
notif: {
   message: {
     user: { id: '123', ... },
     segments: [ { } ],
     changes: {},
     events: []
   },
   subject: 'user:update',
   timestamp: "2016-02-03T17:01:57.393Z' }
},

context: {
 hull: <Instance of Hull Client>
 ship: <Current connector instance if available>,
 req: < Original request, Useful to retreive additional data>
}
*/

app.listen(process.env.PORT||80);

Client-side

The minimal setup for a simple Ship:

  • A javascript file (let’s call it ship.js), that calls Hull.onEmbed() if you’re using the Hull.js library to embed it;
  • A manifest.json file with an index entry, referencing the javascript file.

Connectors are attached to DOM nodes targeted by a CSS selector. This CSS selector is defined by from inside the Dashboard. See below how Developers can define default values for them.

Hull.js will load the file, and call your callback once for each maching element in the page.

minimal-connector
├── ship.js
└── manifest.json

manifest.json

{
  "name" : "minimal-connector",
  "version" : "0.1.0",
  "index" : "ship.js"
}

ship.js

Hull.onEmbed(function(element, deployment, hull){
  //Start your connector here
})

Booting your application

hull.js manages the lifecycle of your Ship when it’s embedded. If your app needs to run some initialization code, you can register a callback function that will be called by hull.js when your app is ready to start.

hull.js has a method called Hull.onEmbed() that you can call in your application code to register this init callback.

For best results, please ensure you’re always using the hull instance that’s passed to you in the callback of Hull.onEmbed(), not the global one.

Example:

<h1>Hello from Minimal Ship</h1>
<script>
Hull.onEmbed(function(element, deployment, hull) {
  console.log('Hello, i am a Ship and i just started');
  console.log('Here is my root element : ', element);
  console.log('and here is my environment: ', deployment);

  Hull.track("event") //BAD : We're using the global Hull instead of the local one.
  hull.track("event"); //GOOD
  hull.share(...) //GOOD. Notice we're using hull instead of Hull

});
</script>

This callback’s first argument is the root DOM node of your Ship and the second argument is a Deployment object that represents the instance of your Ship in the page.

It contains your actual Ship object along with its resources and settings, and the context where the Ship is embedded.

The third object is a full copy of the Hull object, customized for your connector instance. This is what you should use from now on.

Here is an example deployment :

{
  "settings": {
    // Those are Deployment-specific settings.
    // The Ship also has Ship-level settings.
    //
    // The settings below are always present. You can declare more
    // in the "deployment_settings" hash of the Manifest.
    "_multi": false, //wether to embed on every matched selector or just the first
    "_placement": "replace", //bottom|top|before|after|replace, where to embed when selector is found
    "_selector": "#my-ship" //css3 selector where to insert connector
    // ...
  },
  "ship": {
    "id": "554b7b05f89a87a527000180",
    "name": "My Simple Ship",
    "manifest": {
      index: '/ship.js'
      //... Entire manifest.json
    },
    "settings": { ... }, //Configured Settings that are described in manifest
    "resources": { ... }, //configured Resources, such as Quizzes
    "translations" : { ... },
    "manifest_url": "https://example.com/my-connector/manifest.json",
    "index": "https://example.com/my-connector/ship.js",
    ...
  },
  "platform": {
    //... Platform Data
  },
  "organization": {
    //... Organization Data
  }
}

Building Connectors

Ship Settings

When Building Connectors you will want give users a way to edit some settings. The manifest.json lets you define a list of settings to display. It is also used to automatically build the UI for them in Hull’s Dashboard.

manifest.json

{
  "name" : "My Ship",
  "tags" : ["incoming", "outgoing", "batch", "client", "wideSettings", "oneColumn"],
  "description": "This achieves world peace"
  "version" : "0.1.0",
  "picture" : "picture.png",
  "readme" : "readme.md",
  "ui" : true,
  "index" : "ship.js",
  "subscriptions" : [ { "url" : "/notify" } ],
  "schedules": [
    {
      "url": "/daily-job",
      "type": "cron",
      "value": "0 0 * * *",
      "params": {
        "foo": "bar"
      }
    }
  ],
  "settings" : [
    {
      "name"           : "background_color",
      "title"          : "Background Color",
      "description"    : "The Background color of your Ship",
      "type"           : "string",
      "format"         : "color",
      "default"        : "#ffffff"
    },
    {
      "name"           : "logo_url",
      "title"          : "Logo",
      "description"    : "Upload your logo",
      "type"           : "string",
      "format"         : "image",
      "default"        : "http://url.to/my/default/image.jpg"
    },
    {
       "name"          : "awesome",
       "title"         : "Enable Awesomeness",
       "description"   : "Isn't it pretty neat ?",
       "type"          : "boolean",
       "default"       : true
    }
  ],
  "private_settings" : [
    {
      "name" : "api_key",
      "description" : "Super secret API Key",
      "type" : "string"
    }
  ],
  "deployment_settings": [],
  "locales": [ "en"],
  "resources": [{
    "name" : "MyProfileForm",
    "type" : "form"
  }]
}

This settings structure is based on JSON Schema with a few additions for custom formats and validation (Check the Manifest reference section for a full list of options). We’re using a customized version of json-editor to build the UI, so you can try and experiments with their settings.

The values defined by your Users for those settings will be available in the object passed to your init callback for Client-side connectors.

index.html

<script>
Hull.onEmbed(function(element, deployment, hull) {
  var shipSettings = deployment.ship.settings;
  var deploymentSettings = deployment.settings;
  if (shipSettings.awesome) {
    var awesome = document.createElement('div');
    awesome.innerHTML = 'Awesome !';
    element.appendChild(awesome);
  }
});
</script>

Common settings

name

required String.

A name for the Ship. Used for the registry.

tags

optional array

A list of tags describing what the Ship can do. Recognized tags:

  • incoming : Receives data from external service
  • outgoing : Sends data to external service
  • batch : Can process users in Batches
  • client : Has Client-side code
  • wideSettings : Enlarges the Sidebar to make more room for the Settings
  • oneColumn : One column Layout

description

optional String.

A description for the Ship. Used for the registry.

version

required String.

The version of your Ship.

picture

optional String.

A relative path to a picture for the Ship. Used for the registry.

readme

optional String.

A relative path to a markdown file explaining how the connector works, and it’s benefits. Will be shown in the dashboard to help users if available

ui

optional Boolean.

A flag to indicate Hull that there’s no UI to be displayed for a given connector. It’s different from not having client-side code at all. For instance, Connectors only performing tracking will not have a user-facing interface, hence should not show a preview. This flag makes it possible to reflect this in the dashboard.

index

required String.

A relative path to the file that will be injected in the website where the Ship is deployed.

subscriptions

Specifies endpoints on this connector that Hull will send events to.

Currenly sent events are :

  • ‘user_report:update’
  • ‘users_segment:update’

More docs will be released about those soon. For now, look at the Segment Ship

schedules

optional Array

List of endpoints that will be called by Hull on a specified schedule. The “url” is relative to the connectors’s base URL. Optional additional params can be specified.

The endpoint will receive organizatinon, ship, secret variables in the querystring to allow authentication. With hull-node. The Hull middleware makes it automatic and adds a req.hull object with a Hull client and the current ship

"schedules" : [
  {
    "url" : "/sync",
    "type": "cron",
    "value": "0 * * * *"
  }
]

batches

If the special batch tag is found under the tags key in the manifest, the connector should be able to handle export notifications under the /batch endpoint, and process them. The format is the same as when using the Export as JSON function in the dashboard.

settings

optional Array.

Public settings are accessible from the javascript page, even for anonymous users. They’re right place to put button colors or Images for instances.

private_settings

optional Array.

Settings that are not exposed to end users. Use them for server side integration that need to access private information like credentials for third party services.

deployment_settings

optional Array.

Same as settings, but those settings are scoped to a deployment and not a Ship instance. When you have multiple platforms, the same connector can be deployed to many platforms. In this case, each deployment has a separate set of those. They are configured from the Platform’s settings screen.

locales

optional Array.

List of supported locales for your Ship. locale file should be located in the locales folder.

resources

optional Array.

Define what are that required resources for the Ship. A resource can be a form form, a quiz or an instant_win.

Settings field types

Used to describe settings, deployment_settings and private_settings.

string

{
  "name"        : "title",
  "title"       : "Main Title",
  "description" : "The Main title for your app",
  "type"        : "string",
  "default"     : "Hello",
}

color

{
  "name"        : "background_color",
  "title"       : "Background Color",
  "description" : "How the page background should look like",
  "type"        : "string",
  "format"      : "color",
  "default"     : "#fffFFF"
}

image

{
  "name"        : "logo",
  "type"        : "string",
  "format"      : "image"
}

enums

{
  "name"        : "position",
  "type"        : "string",
  "enum"        : ["top", "bottom", "left", "right"],
  "default"     : "top"
}

boolean

{
  "name"        : "is_happy",
  "type"        : "boolean",
  "default"     : true
}

number

{
  "name"        : "points",
  "type"        : "number",
  "default"     : 32
}

range

{
  "name"        : "background_image_blur",
  "type"        : "number",
  "format"      : "range",
  "default"     : 3,
  "maximum"     : 10,
  "minimum"     : 0,
  "multipleOf"  : 0.1
}

segment

A single Hull Segment

{
  "name"        : "segment",
  "type"        : "string",
  "format"      : "segment"
}

segments

An array of Hull Segments

{
  "name"        : "segment",
  "type"        : "array",
  "format"      : "segment"
}

trait

A single, already existing Hull Trait

{
  "name"        : "trait",
  "type"        : "string",
  "format"      : "trait"
  "options": {
    "source": "hubspot" //Scopes the trait to a specific group.
    "placeholder": "Pick a Hull field id" //Customize placeholder text
  }

}

traits

An array of already existing Hull Traits

{
  "name"        : "trait",
  "format"      : "trait"
  "type"        : "array",
  "options": {
    "source": "hubspot" //Scopes the trait to a specific group.
    "placeholder": "Pick a Hull field id" //Customize placeholder text
  }
}

editable trait

A Hull Trait that can be picked or created

{
  "name"        : "trait",
  "type"        : "string",
  "format"      : "trait",
  "options": {
    "source": "hubspot", //Scopes the trait to a specific group.
    "placeholder": "Enter a Hull field id", //Customize placeholder text
    "allowCreate": true //Allows trait creation
  }
}

event

An single Hull Event

{
  "name"        : "events",
  "format"      : "event"
  "type"        : "string",
}

events

An array of Hull Event

{
  "name"        : "events",
  "format"      : "event"
  "type"        : "array",
}

select

Select a value from a list of dynamically loaded options.

{
  "name" : "city",
  "type" : "string",
  "format" : "select",
  "options" : {
    "loadOptions": "/select/cities",
    "allowCreate": true,
    "placeholder": "Pick a city"
  }
}

Options will be loaded from the “loadOptions” endpoint (relative to the connector’s base URL). The endpoint must allow CORS and return an array of option with the following format :

{
  "options" : [
    { "value" : "1", "label" : "One" },
    { "value" : "2", "label" : "Two" }
  ]
}

Options can also be grouped with the following format :

{
  {
    "label" : "Group 1",
    "options" : [
      { "value" : "1", "label" : "One" },
      { "value" : "2", "label" : "Two" }
    ]
  },
  {
    "label" : "Group 2",
    "options" : [
      { "value" : "3", "label" : "Three" },
      { "value" : "4", "label" : "Four" }
    ]
  }
}

Use "type" : "array" to allow the selection of multiple values.

object

{
  "name"        : "sharing_buttons",
  "type"        : "object",
  "properties"  : {
    "facebook"    : {
      "title"       : "Facebook",
      "type"        : "boolean",
      "default"     : true
    },
    "twitter"     : {
      "title"       : "Twitter",
      "type"        : "boolean",
      "default"     : true
    }
  }
}

table

{
  "name": "footer_links",
  "title": "Footer Links",
  "type": "array",
  "format": "table",
  "items": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "title": "Link Name"
      },
      "url": {
        "type": "string",
        "title": "Link URL",
        "format": "uri"
      }
    },
    "required": [ "name", "url" ]
  },
  "default": [
    {
      "name": "A Link here 1",
      "url": "/"
    },
    {
      "name": "A Link here 2",
      "url": "/"
    }
  ]
}

hidden

Used to store settings that can be manipulated, saved but have no UI to edit. Typically tokens.

{
  "name" : "dont_show_me",
  "description" : "Settings with hidden format are not displayed in the Settings editor",
  "type" : "string",
  "format" : "hidden"
}

title

Used to display accented color titles in the Sidebar. No user-editable field

    {
      "name":"header_a",
      "title": "This is a title",
      "type": "string",
      "format": "title"
    }

instructions

This is a special format meant to provide dynamic instructions. Write content using Mustache syntax, with the following variables:

{
  "name": "Ship's User-Given name",
  "id": "Ship's ID - also used as a Key most of the time",
  "secret": "Ship's Secret. used with the Key and OrgDomain to authenticate to the API",
  "orgDomain": "Current Organization's domain",
  "source_url": "Url where the connector is hosted. Generally the domain you want to use when using server side connectors",
  "manifest": "The entire manifest object",
  "index": "connector's index.js file if specified"
}

Here’s how to use it. You also should put this in private_settings in case you put sensiteve content to be sure you avoid leaks. Markdown is supported but newlines need to be encoded as “\n”

"private_settings": [
  {
    "name": "instructions",
    "headerTemplate": "Copy the following URL and paste it in the Webhooks integration in Segment.com:\n`{{source_url}}segment?organization={{orgDomain}}&secret={{secret}}&ship={{id}}`",
    "type": "string",
    "format": "information"
  }
]

Client-side settings

Wording and Locales

You can also allow your User to change the wording inside your app via the Ship’s locales settings.

To enable this feature, just include a “locales” entry to your manifest.json with a list of available locales.

minimal-with-locales
├── ship.js
├── manifest.json
└── locales
     └── en.json

manifest.json

{
  "name"    : "My Ship with Locales",
  "version" : "0.3.0",
  "index"   : "ship.js",
  "locales" : ["en"]
}

locales/en.json

{
  "hello" : "Hello"
}

If a locales key is found in your Manifest, Hull will automatically fetch the locale json files and allow the user to edit the values in the Dashboard.

To use those values in your application, look for the translations key inside your Ship object.

<script>
Hull.onEmbed(function(rootNode, deployment, hull) {
  var translations = deployment.ship.translations;
  console.log(translations.hello + " from this Ship");
});
</script>

Resources

Resources are objects on the Hull API that are required by your Ship to run.

Examples of resources can be :

  • achievement An Instance of an Achievement object
  • quiz An Instance of a Quiz object
  • instant_win An Instance of an Instant Win object
  • form A configurable Hull Form

Resources referenced in the Manifest are automatically created and linked when the Ship is created.

manifest.json

{
  "name" : "minimal-with-resources",
  "version" : "0.1.0",
  "index" : "ship.js",
  "resources" : [
    {
      "name" : "profileForm",
      "type" : "form"
    }
  ]
}

When the connector is added to the Dashboard, a Hull Form object will be created and a section added to the Dashboard to edit the Form’s fields.

Then to use this Form object in your app :

ship.html

<script>
Hull.onEmbed(function(rootNode, deployment) {
  var profileForm = deployment.ship.resources.profileForm;
  console.log("Here is my Form object: ", profileForm);
});
</script>