Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ac-koa-hipchat

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ac-koa-hipchat

A Koa.js library for building Atlassian Connect HipChat add-ons

  • 0.2.7
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
46
increased by2200%
Maintainers
1
Weekly downloads
 
Created
Source

Codeship Status for rbergman/ac-koa-hipchat

What is this?

A Node.js and Koa.js-based library for building HipChat Connect add-ons, built up from lower-level libraries such as ac-node, ac-node-hipchat, and ac-koa.

This library is designed to make writing Node.js-based add-ons as simple as possible while improving on the design limitations of its predecessor, Atlassian Connect Express - HipChat.

This is an early, alpha-quality release, but can be used to build real add-ons today. Future versions may include backward-incompatible changes.

Getting started

For a simple alternative to the following set up instructions, you may consider using the Vagrant starter project to get up and running quickly.

Dependencies

If you're already comfortable with Node.js, the main thing you need to know to get started is that Koa (and therefore this library) requires Node v0.11.x. Using nvm to manage multiple instances of Node is recommended. If you need more help with Node, nvm, or npm, please start with their public documentation.

By default, ac-koa-hipchat expects Redis to be available, for the persistence of tenant installations. You can provide your own persistent store implementation if necessary, though that is a more advanced topic that will be treated elsewhere. See the ac-node library for the Redis store implementation and compatibility tests as a guide if you need a custom store, otherwise make sure you have Redis installed and running locally.

A first add-on

Writing basic HipChat add-ons with ac-koa-hipchat requires very little code to get up and running. Here's an example of a simple yet complete add-on, in two files:

web.js

var ack = require('ac-koa').require('hipchat');
var pkg = require('./package.json');
var app = ack(pkg);

var addon = app.addon()
  .hipchat()
  .allowRoom(true)
  .scopes('send_notification');

addon.webhook('room_message', /^\/hello$/, function *() {
  yield this.roomClient.sendNotification('Hi, ' + this.sender.name + '!');
});

app.listen();

package.json

{
  "name": "ac-koa-hipchat-greeter",
  "displayName": "HipChat Greeter Example Add-on",
  "description": "Greets people when they type /hello in chat",
  "version": "0.1.0",
  "author": {
    "name": "Atlassian",
    "url": "http://atlassian.com"
  },
  "license": "Apache 2.0",
  "engines": {
    "node": "~0.11.13"
  },
  "scripts": {
    "web": "node --harmony web.js",
    "web-dev": "nodemon --harmony -e js,css,hbs,html",
    "tunnel": "ngrok 3000"
  },
  "development": {
    "port": 3000
  },
  "production": {
    "localBaseUrl": "https://hipchat-greeter-example.herokuapp.com",
    "redisEnv": "REDISCLOUD_URL",
    "port": "$PORT"
  },
  "dependencies": {
    "ac-koa": "^0.2.3",
    "ac-koa-hipchat": "^0.2.4"
  }
}

Running the server

To run this example yourself, add these files to a new directory and run the following commands there:

$ npm install
$ npm run web

If the server started as expected, you'll see something like the following emitted:

info: Atlassian Connect add-on started at http://hostname.local:3000

To double check that the server is running correctly, try requesting it's add-on descriptor:

$ curl http://hostname.local:3000/addon/capabilities

A successful request will return a HipChat capabilities descriptor for the add-on.

Optimizing your dev loop

To get the server to restart automatically when code changes, nodemon is recommended, and can be installed by simply running the following:

$ npm install -g nodemon

Then you can run this command to start the server for development:

$ npm run web-dev

In the example above, nodemon is set up to monitor js, json, css, and hbs (handlebars) files. If your add-on use additional file types, you may want to add them to the web-dev script configuration in package.json.

If you encounter errors while following these steps, double check that you're using Node v0.11.x and that all of the above dependencies installed correctly.

Preparing the add-on for installation

Now that you have a server running, you'll want to try it somehow. The next step is different depending on whether you're going to be developing with hipchat.com or a private HipChat instance being hosted behind your corporate firewall.

Developing with HipChat.com

The easiest way to test with hipchat.com while developing on your local machine is to use ngrok. Download and install it now if you need to -- it's an amazing tool that will change the way you develop and share web applications.

You may also want to set up your own ngrok account to access advanced features like being able to specify custom subdomains, but it's not necessary.

Now you can start a secure tunnel (in a new console) to your add-on that's accessible by the outside world, which will allow you to install it at hipchat.com while you test it:

$ npm run tunnel

That command will start ngrok and output the address of your new tunnel, which should look something like https://3a4bfceb.ngrok.com. This will be the value you use for your "local base url" needed by the Installation step.

While ngrok will forward both HTTP and HTTPS, for the protection of you and your HipChat group members, you should always use HTTPS when running your add-on on the public internet.

Developing with a private server

To install your add-on on a private HipChat server, both the add-on server and HipChat server need to be able to connect to each other via HTTP or HTTPS on your local network. Simply determine an HTTP url that your HipChat server can use to connect to your locally running add-on, and use that as the value of your "local base url" needed by the Installation step.

If all goes well, you won't have to change anything from the defaults, as ac-koa-hipchat will simply attempt to use the OS's hostname to build the local base url, which may already be good enough for your private network.

Installation

Configuring the add-on's local base url

Now, we need to tell the add-on server where it's running so that it can successfully be installed. You can do this in one of the following ways, using the local base url determined in the prior step, appropriate to your environment:

  1. Set the LOCAL_BASE_URL environment variable when you start the server:
$ LOCAL_BASE_URL=https://xxxxxxxx.ngrok.com npm run web-dev
  1. Add it to your add-on's package.json configuration in the development section:
"development": {
  "localBaseUrl" : "https://xxxxxxxx.ngrok.com",
  "port": 3000
},

The ac-koa-hipchat library first looks for the configuration values it needs as environment variables, and then in the current runtime environment section of the configuration. If the local base url isn't not found in either location, it defaults to http://<hostname>:$PORT, but when advertising that address it can't be properly installed with hipchat.com.

Which section of the configuration is used (e.g. production, development, test, etc) depends on the value of the environment variable NODE_ENV, whose value defaults to development. When running your server on a PaaS such as Heroku, you'll want to set NODE_ENV=production.

When properly configured, you'll see the server report the new local base url when it starts up:

info: Atlassian Connect add-on started at https://xxxxxxxx.ngrok.com

Note: by signing up for an ngrok account, you can specify a generally stable, custom subdomain for even easier iterative development. See ngrok for more information.

Manually installing the add-on using HipChat's admin UI

To install your add-on into HipChat, you have to register your addon's capabilities descriptor.

HipChat add-ons can operate inside a room or within the entire account. When developing, you should probably register your add-on inside a room you've created just for testing. Also, you can only register add-ons inside a room where you are an administrator.

To register your add-on descriptor, navigate to the rooms administration page at https://<your-account>.hipchat.com/rooms (or whatever url your private server is running at, if appropriate). Then select one of your rooms in the list. In the following page, select Integrations in the sidebar, and then click the "Build and install your own integration" link at the bottom of the page:

Installation Screenshot

Paste your descriptor url in the Integration URL field of the opened dialog and then click Add integration. This will initiate the installation of your add-on for that room.

Example Projects

The example illustrated above comes from the following example project:

See these additional add-ons for more complete examples:

  • Hearsay (demonstrates a configuration UI)
  • Mailroom (demonstrates third party webhook integration)
  • Sassy (demonstrates multiple /commands)

Library Features

This library, in conjunction with it's product-agnostic base library ac-koa, provides help with many aspects of add-on development, such as:

  • Support for mounting multiple addons in a single Koa app
  • Configuration of commonly required Koa middleware
  • Choice of programmatic HipChat add-on descriptor builder or providing a full or partial descriptor object literal
  • Multitenant registration and data partioning
  • High-level conveniences for mounting webhook handlers
  • A REST API client with built-in OAuth2 token acquisition and refresh
  • JWT authentication validation, refresh, and token generation for web UI routes (e.g. the configurable capability)
  • A tenant-aware API for dynamically adding and removing room webhooks

In the documentation below, we use the terms ctx and 'Koa context' interchangably to refer to the context object that contains both standard Koa request/response data and objects and this library's request objects and services.

Creating a Koa.js app

A convenience method for creating a Koa app augmented with the ability to define add-ons is provided by the following boilerplate setup:

var ack = require('ac-koa').require('hipchat');
var pkg = require('./package.json');
var app = ack(pkg);

In this example, the app object is the configured Koa application, ready to define one or more add-ons in following steps.

Add-on definition

Add-ons can be defined on the app object in two ways:

Pass a full or partial descriptor literal to to the app.addon() method

app.addon({
  // optional descriptor metas here (taken from package.json when not overridden here)
  capabilities: {
    // standard HipChat capabilities descriptor body
  }
});

When using this style, you're responsible for manually configuring routes to handle any webhooks defined.

Pass nothing to app.addon() and use the more concise descriptor builder API

If you pass nothing to the addon method, a descriptor builder API will be applied that allows critical sections of the descriptor to be defined with builder methods. This is particularly useful for conveniently defining webhook handlers while also building the descriptor.

For example, the following defines a fully functional descriptor while also associating a webhook handler:

var addon = app.addon()
  .hipchat()
  .allowRoom(true)
  .scopes('send_notification');

addon.webhook('room_message', /^\/hello$/, function *() {
  yield this.roomClient.sendNotification('Hi, ' + this.sender.name + '!');
});

Listening to add-on events

Any webhooks defined through the builder interface, as well as the automatically configured install and uninstall webhooks, generate events on the addon object. To listen to webhook events with Koa-style generator functions, you can use the following API:

addon.onWebhook('install', function *() {
  // use the koa context (this) to access request, response, tenant info, and tenant services normally
});

Defining webhooks dynamically

Sometimes, you won't know what webhooks you want to define at the time that the descriptor is defined or built. This may happen if your add-on creates webhooks in response to add-on configuration, for example. In this case, you can add or remove webhooks dynamically using the tenantWebhooks service API available on any Koa add-on context.

this.tenantWebhooks.add('room_message', /^\/hello/i);

To handle this event, simply define a webhook listener on the addon:

addon.onWebhook('room_message', function *() {
  // handle the webhook, dispatching to an appropriate subroutine based on data in the query string and webhook body
});

Multiple add-on support

More than one add-on can be mounted at a time in a single Koa app by giving each a unique mount scope. For example:

var addon1 = app.addon('addon1')
  .hipchat()
  .key('addon1-key')
  .name('Addon 1')
  .allowRoom(true)
  .scopes('send_notification');

var addon2 = app.addon('addon2')
  .hipchat()
  .key('addon2-key')
  .name('Addon 2')
  .allowRoom(true)
  .scopes('send_notification');

The scope given to each addon() method is automatically used for both data partioning and route disambiguation.

Multitenancy

One add-on can be installed with multiple HipChat OAuth2 clients, referred to here as 'tenants'. In practice, a tenant is either a HipChat room or group, depending on the installation scope of the add-on.

Tenant registration and information

Tenant installation and uninstallation is handled automatically by the library, which configures the necessary routes and handlers for each mounted add-on. Each installation results in the registration information for that tenant to be verified and stored for later use, including the tenant's shared secret, used for bi-directional authentication.

Add-on implementations are given access to the tenant information object in every Koa context for routes and webhook listeners in which this library is involved.

ctx.tenant
FieldDescription
idThis tenant's id.
groupThis tenant's group id.
secretThis tenant's shared secret.
roomThis tenant's room id, if installed in a single room.
webhookTokenA secret token added to all dynamically generated webhooks, as an extra measure of security.
linksA collection of this tenant's relevant URLs.
links.capabilitiesThis tenant's capabilities descriptor URL.
links.baseThis tenant's base URL.
links.apiThis tenant's base API URL.
links.tokenThis tenant's OAuth2 token generation URL.

Tenant authentication

This library handles bi-direction authentication between tenants and add-ons. It provides the following facilities:

Inbound JWT signature verification

For add-on routes that provide web UI to the tenant, such as the one defined in an add-on's configurable capabilitity, use the addon object's authenticate() middleware to protect your route. This middleware will then verify that requests have a valid JWT signature provided either as the signed_request query parameter or in a standard HTTP Authorization header with the format Authorization: JWT token=<jwt-token-value>.

For example, one would secure an addon's /configure route with this middleware as follows:

addon.get('/configure',
  addon.authenticate(),
  function *() {
    // Normal Koa route handling here...
  }
);

Requests successfully passing through this middleware will have the following object available on the Koa context:

ctx.authentication
FieldDescription
issuerThe authenticated tenant's id.
issuedThe timestamp at which the token was generated.
userIdThe id of the user making the request.
expiryThe time at which the token should be expired.
contextAn additional request context object sent by the tenant as part of the signed data. This may contain the current user's timezone in a field named tz.
tokenA refreshed version of the JWT token, suitable for use in subsequent requests over Ajax or as form or link parameters. See the Hearsay example add-on for a demonstration of this technique. We prefer this approach to maintaining state for iframed add-on UI over cookies due to some anti-click-jacking browser security models that prevent cookies from being set in cross-domain iframes.
Outbound request token handling

Outbound requests to the tenant's REST APIs require an current OAuth2 bearer token, which must be refreshed via the tenant's token API when it expires. As long as the add-on uses the ctx.tenantClient or ctx.roomClient APIs, this token management is handled automatically. See below for information about the these services.

Tenant services

For convenient add-on implementation, several service objects are attached to the Koa context that provide tenant-aware operations.

Tenant data storage

In order to support multiple tenants concurrently, a tenant-aware data storage abstraction is used throughout the library to partition tenant data, and a derivative of a tenant's unique storage object is provided with each Koa context, allowing add-ons access to a simple, partioned location to store basic key/value information. Storage of more structured data in alternative data stores is left as an exercise for each add-on, though every Koa context contains the full tenant data model, making such manual data partioning straightforward.

ctx.tenantStore
MethodDescription
get(key)Gets a value for a given key. Returns a promise.
set(key, value)Sets a value for a given key. Returns a promise.
del(key)Deletes a value for a given key. Returns a promise.
all()Gets all values in the current storage scope. Returns a promise.
narrow(scope)Creates and returns a substore narrowed by the given scope.
Tenant REST Client

A tenant REST client is provided that automatically handles the construction of authenticated requests for each of opertions available in the HipChat API v2.

ctx.tenantClient

A HipChat API v2 REST client API with automatic automatic Oauth2 token acquisition and refresh.

MethodDescription
sendNotification(roomIdOrName, message, options)Sends a notification message to a room by name or id. The optional options object may include any or all of color, notify, or format -- see the API docs for valid values. Returns a promise.
createWebhook(roomIdOrName, definition)Creates a new webhook in a specific room given a definition -- see the API docs for the definition fields. Returns a promise.
deleteWebhook(roomIdOrName, webhookId)Deletes an existing webhook by id in a specific room. Returns a promise.
TODO(Implement and document the remaining API methods)
ctx.roomClient

A thin wrapper around the tenantClient object that's provided in room-specific request contexts. Only the methods of the tenantClient that require a room id are exposed on this object, with the room id already partially applied for convenience.

MethodDescription
sendNotification(message, options)Sends a notification message to the current room. The optional options object may include any or all of color, notify, or format -- see the API docs for valid values. Returns a promise.
TODO(Implement and document the remaining API methods)
ctx.tenantWebhooks

A high-level API for adding or removing webhooks for a given tenant, in such a way that this library can manage any required routing, authentication, and dispatching necessary to provide a similar level of service as is enjoyed by statically defined webhooks (i.e. those defined as part of the capabilities decriptor when the addon is defined at server startup).

See the Hearsay example add-on for a functioning example of the webhook manager API.

MethodDescription
get(name)TODO
get(roomId, name)TODO
add(event)TODO
add('room_message', pattern)TODO
add(definition)TODO
add(roomId, event)TODO
add(roomId, 'room_message', pattern)TODO
add(roomId, definition)TODO
remove(name)TODO
remove(roomId, name)TODO
Webhook data extraction and normalization

Webhook listeners added via either addon.webhook(...) (for webhooks defined using the add-on builder API convenience method) or addon.onWebhook(...) (for dynamic or non-builder based webhook listeners) are provided with both raw and normalized views of the webhook body. Since not all webhooks organize their data the same way, extracted and normalized webhook fields are attached directly to the Koa context, along side the raw webhook object. Not all webhooks provide all fields and some of these fields are themselves complex objects, so consult the API docs for information about what to expect when.

FieldDescription
ctx.webhookThe raw webhook payload.
ctx.webhookIdThe webhook id.
ctx.roomThe relevant room model object.
ctx.senderThe relevant sender model object.
ctx.messageThe message object, if any. Only in room_notification and room_message webhooks.
ctx.contentThe message field of the message object, if any. Only in room_notification and room_message webhooks.
ctx.topicThe room topic, if any. Only in room_topic_change webhooks.

Anatomy of an add-on

Here's a closer look at the original example add-on, with comments for illustration:

// Require the 'ac-koa' module, and then tell it to load the 'hipchat' adapter
// from 'ac-koa-hipchat'
var ack = require('ac-koa').require('hipchat');
// Require our package.json file, which doubles as the configuration from which
// we'll generate the add-on descriptor and server's runtime parameters
var pkg = require('./package.json');
// Create the base Koa app, via an 'ac-koa' factory method that helps preconfigure
// and decorate the app object
var app = ack(pkg);

// Now build and mount an AC add-on on the Koa app; we can either pass a full or
// partial descriptor object to the 'addon()' method, or when we provide none, as
// in this example, we can instead create the descriptor using a product-specific
// builder API
var addon = app.addon()
  // Use the hipchat descriptor builder
  .hipchat()
  // Indicate that the descriptor should mark this as installable in rooms
  .allowRoom(true)
  // Provide the list of permissions scopes the add-on requires
  .scopes('send_notification');

// Subscribe to the 'room_enter' webhook, and provide an event listener.  Under
// the covers, this adds a webhook entry to the add-on descriptor, mounts a common
// webhook endpoint on the Koa app, and brokers webhook POST requests to the event
// listener as appropriate
addon.webhook('room_enter', function *() {
  // 'this' is a Koa context object, containing standard Koa request and response
  // contextual information as well as hipchat-specific models and services that
  // make handling the webhook as simple as possible
  yield this.roomClient.sendNotification('Hi, ' + this.sender.name + '!');
});

// Now that the descriptor has been defined along with a useful webhook handler,
// start the server 
app.listen();

Production deployment using a PaaS

TODO

Keywords

FAQs

Package last updated on 11 Sep 2014

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc