UNPKG

23.9 kBMarkdownView Raw
1[ ![Codeship Status for rbergman/ac-koa-hipchat](https://codeship.io/projects/22bfa080-fbc3-0131-d6f8-5a73486b8860/status)](https://codeship.io/projects/29249)
2
3# What is this?
4
5A [Node.js](http://nodejs.org) and [Koa.js](http://koajs.com)-based library for building [HipChat Connect add-ons](https://www.hipchat.com/docs/apiv2/addons), built up from lower-level libraries such as [ac-node](rbergman/ac-node), [ac-node-hipchat](rbergman/ac-node-hipchat), and [ac-koa](rbergman/ac-koa).
6
7This 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](https://www.npmjs.org/package/atlassian-connect-express-hipchat).
8
9This is an early, alpha-quality release, but can be used to build real add-ons today. Future versions may include backward-incompatible changes.
10
11# Getting started
12
13## Dependencies
14
15If 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](https://github.com/creationix/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.
16
17By 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](rbergman/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.
18
19## A first add-on
20
21Writing 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:
22
23### web.js
24
25```
26#!javascript
27
28var ack = require('ac-koa').require('hipchat');
29var pkg = require('./package.json');
30var app = ack(pkg);
31
32var addon = app.addon()
33 .hipchat()
34 .allowRoom(true)
35 .scopes('send_notification');
36
37addon.webhook('room_enter', function *() {
38 yield this.roomClient.sendNotification('Hi, ' + this.sender.name + '!');
39});
40
41app.listen();
42```
43
44### package.json
45
46```
47#!json
48
49{
50 "name": "hipchat-greeter-example",
51 "displayName": "HipChat Greeter Example Add-on",
52 "description": "Greets people when they join a HipChat room",
53 "version": "0.1.0",
54 "author": {
55 "name": "Your Name",
56 "url": "http://yourcompany.com"
57 },
58 "license": "Apache 2.0",
59 "engines": {
60 "node": "~0.11.13"
61 },
62 "scripts": {
63 "web": "node --harmony web.js"
64 "web-dev": "nodemon --harmony -e js,json,css,hbs web.js",
65 "tunnel": "ngrok 3000"
66 },
67 "development": {
68 "port": 3000
69 },
70 "production": {
71 "localBaseUrl": "https://hipchat-greeter-example.yourpaas.com",
72 "redisEnv": "NAME_OF_ENV_VAR_CONTAINING_REDIS_URL",
73 "port": "$PORT"
74 },
75 "dependencies": {
76 "ac-koa": "^0.1.4",
77 "ac-koa-hipchat": "^0.1.6"
78 }
79}
80```
81
82## Running the server
83
84To run this example yourself, add these files to a new directory and run the following commands there:
85
86```
87#!bash
88
89$ npm install
90$ npm run web
91```
92
93If the server started as expected, you'll see something like the following emitted:
94
95```
96#!bash
97
98info: Atlassian Connect add-on started at http://hostname.local:3000
99```
100
101To double check that the server is running correctly, try requesting it's add-on descriptor:
102
103```
104#!bash
105
106$ curl http://hostname.local:3000/addon/capabilities
107```
108
109A successful request will return a HipChat capabilities descriptor for the add-on.
110
111## Optimizing your dev loop
112
113To get the server to restart automatically when code changes, [nodemon](https://github.com/remy/nodemon) is recommended, and can be installed by simply running the following:
114
115```
116#!bash
117
118$ npm install -g nodemon
119```
120
121Then you can run this command to start the server for development:
122
123```
124#!bash
125
126$ npm run web-dev
127```
128
129In 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`.
130
131If 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.
132
133## Preparing the add-on for installation
134
135Now 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.
136
137### Developing with HipChat.com
138
139The easiest way to test with hipchat.com while developing on your local machine is to use [ngrok](https://ngrok.com). 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.
140
141You 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.
142
143Now you can start a secure tunnel 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:
144
145```
146#!bash
147
148$ npm run tunnel
149```
150
151That 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.
152
153While 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.
154
155### Developing with a private server
156
157To 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.
158
159If 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.
160
161## Installation
162
163### Configuring the add-on's local base url
164
165Now, 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:
166
1671. Set the `LOCAL_BASE_URL` environment variable when you start the server:
168
169```
170#!bash
171
172$ LOCAL_BASE_URL=https://3a4bfceb.ngrok.com npm run web-dev
173```
174
1752. Add it to your add-on's `package.json` configuration in the `development` section:
176
177```
178#!json
179
180"development": {
181 "localBaseUrl" : "https://3a4bfceb.ngrok.com",
182 "port": 3000
183},
184```
185
186The `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.
187
188Which 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](http://heroku.com), you'll want to set `NODE_ENV=production`.
189
190When properly configured, you'll see the server report the new local base url when it starts up:
191
192```
193#!bash
194
195info: Atlassian Connect add-on started at https://3a4bfceb.ngrok.com
196```
197
198### Manually installing the add-on using HipChat's admin UI
199
200To install your add-on into HipChat, you have to register your addon's capabilities descriptor.
201
202HipChat 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.
203
204To 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:
205
206![Installation Screenshot](https://s3.amazonaws.com/uploads.hipchat.com/10804/124261/vSnwkHxO1mNue2C/upload.png)
207
208At the bottom of the page, you'll find the `Add new private integration` form. Paste your descriptor url in the `Capabilities URL` field and then click `Add integration`. This will initiate the installation of your add-on for that room.
209
210# Example Projects
211
212The example illustrated above comes from the following example project:
213
214* https://bitbucket.org/rbergman/ac-koa-hipchat-greeter
215
216See these additional add-ons for more complete examples:
217
218* https://bitbucket.org/rbergman/ac-koa-hipchat-hearsay
219* __TODO__: more examples :D
220
221# Library Features
222
223This library, in conjunction with it's product-agnostic base library `ac-koa`, provides help with many aspects of add-on development, such as:
224
225* Support for mounting multiple addons in a single Koa app
226* Configuration of commonly required Koa middleware
227* Choice of programmatic HipChat add-on descriptor builder or providing a full or partial descriptor object literal
228* Multitenant registration and data partioning
229* High-level conveniences for mounting webhook handlers
230* A REST API client with built-in OAuth2 token acquisition and refresh
231* JWT authentication validation, refresh, and token generation for web UI routes (e.g. the `configurable` capability)
232* A tenant-aware API for dynamically adding and removing room webhooks
233
234In 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.
235
236## Multiple add-on support
237
238_Note: The builder support described in this section this may not yet be fully implemented, though it should at least be possible when providing object literal descriptors._
239
240More than one add-on can be mounted at a time in a single Koa app by giving each a unique mount scope. For example:
241
242```
243#!javascript
244
245var addon1 = app.addon('addon1')
246 .hipchat()
247 .key('addon1-key')
248 .name('Addon 1')
249 .allowRoom(true)
250 .scopes('send_notification');
251
252var addon2 = app.addon('addon2')
253 .hipchat()
254 .key('addon2-key')
255 .name('Addon 2')
256 .allowRoom(true)
257 .scopes('send_notification');
258```
259
260The scope given to each `addon()` method is automatically used for both data partioning and route disambiguation.
261
262## Addon definition
263
264__TODO__: Cover both object literal and builder forms of add-on definition, plus the builder API docs and subscription of dynamic webhook listeners.
265
266## Multitenancy
267
268One 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.
269
270### Tenant registration and information
271
272Tenant 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.
273
274Add-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.
275
276#### `ctx.tenant`
277
278Field | Description
279-------------------: | --------------------------------------------------------------------------------------------
280`id` | This tenant's id.
281`group` | This tenant's group id.
282`secret` | This tenant's shared secret.
283`room` | This tenant's room id, if installed in a single room.
284`webhookToken` | A secret token added to all dynamically generated webhooks, as an extra measure of security.
285`links` | A collection of this tenant's relevant URLs.
286`links.capabilities` | This tenant's capabilities descriptor URL.
287`links.base` | This tenant's base URL.
288`links.api` | This tenant's base API URL.
289`links.token` | This tenant's OAuth2 token generation URL.
290
291### Tenant authentication
292
293This library handles bi-direction authentication between tenants and add-ons. It provides the following facilities:
294
295#### Inbound JWT signature verification
296
297For 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>`.
298
299For example, one would secure an addon's `/configure` route with this middleware as follows:
300
301```
302#!javascript
303
304addon.get('/configure',
305 addon.authenticate(),
306 function *() {
307 // Normal Koa route handling here...
308 }
309);
310```
311
312Requests successfully passing through this middleware will have the following object available on the Koa context:
313
314#### `ctx.authentication`
315
316Field | Description
317-------------------: | --------------------------------------------------------------------------------------------
318`issuer` | The authenticated tenant's id.
319`issued` | The timestamp at which the token was generated.
320`userId` | The id of the user making the request.
321`expiry` | The time at which the token should be expired.
322`context` | An 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`.
323`token` | A refreshed version of the JWT token, suitable for use in subsequent requests over Ajax or as form or link parameters. See the [Hearsay](https://bitbucket.org/rbergman/ac-koa-hipchat-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.
324
325#### Outbound request token handling
326
327Outbound 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.
328
329### Tenant services
330
331For convenient add-on implementation, several service objects are attached to the Koa context that provide tenant-aware operations.
332
333#### Tenant data storage
334
335In 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.
336
337#### `ctx.tenantStore`
338
339Method | Description
340-------------------: | --------------------------------------------------------------------------------------------
341`get(key)` | Gets a value for a given key. Returns a promise.
342`set(key, value)` | Sets a value for a given key. Returns a promise.
343`del(key)` | Deletes a value for a given key. Returns a promise.
344`all()` | Gets all values in the current storage scope. Returns a promise.
345`narrow(scope)` | Creates and returns a substore narrowed by the given scope.
346
347#### Tenant REST Client
348
349A tenant REST client is provided that automatically handles the construction of authenticated requests for each of opertions available in the [HipChat API v2](https://www.hipchat.com/docs/apiv2).
350
351#### `ctx.tenantClient`
352
353A [HipChat API v2](https://www.hipchat.com/docs/apiv2) REST client API with automatic automatic Oauth2 token acquisition and refresh.
354
355Method | Description
356----------------------------------------------------: | --------------------------------------------------------------------------------------------
357`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](https://www.hipchat.com/docs/apiv2/method/send_room_notification) for valid values. Returns a promise.
358`createWebhook(roomIdOrName, definition)` | Creates a new webhook in a specific room given a definition -- see the [API docs](https://www.hipchat.com/docs/apiv2/method/create_webhook) for the definition fields. Returns a promise.
359`deleteWebhook(roomIdOrName, webhookId)` | Deletes an existing webhook by id in a specific room. Returns a promise.
360__TODO__ | (Implement and document the remaining API methods)
361
362#### `ctx.roomClient`
363
364A 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.
365
366Method | Description
367----------------------------------------------------: | --------------------------------------------------------------------------------------------
368`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](https://www.hipchat.com/docs/apiv2/method/send_room_notification) for valid values. Returns a promise.
369__TODO__ | (Implement and document the remaining API methods)
370
371#### `ctx.tenantWebhooks`
372
373A 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).
374
375See the [Hearsay](https://bitbucket.org/rbergman/ac-koa-hipchat-hearsay) example add-on for a functioning example of the webhook manager API.
376
377Method | Description
378----------------------------------------------------: | --------------------------------------------------------------------------------------------
379`get(name)` | __TODO__
380`get(roomId, name)` | __TODO__
381`add(event)` | __TODO__
382`add('room_message', pattern)` | __TODO__
383`add(definition)` | __TODO__
384`add(roomId, event)` | __TODO__
385`add(roomId, 'room_message', pattern)` | __TODO__
386`add(roomId, definition)` | __TODO__
387`remove(name)` | __TODO__
388`remove(roomId, name)` | __TODO__
389
390#### Webhook data extraction and normalization
391
392Webhook 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](https://www.hipchat.com/docs/apiv2/webhooks) for information about what to expect when.
393
394Field | Description
395--------------------: | ----------------------------------------------------------------------------------------------------------
396`ctx.webhook` | The raw webhook payload.
397`ctx.webhookId` | The webhook id.
398`ctx.room` | The relevant room model object.
399`ctx.sender` | The relevant sender model object.
400`ctx.message` | The message object, if any. Only in `room_notification` and `room_message` webhooks.
401`ctx.content` | The message field of the message object, if any. Only in `room_notification` and `room_message` webhooks.
402`ctx.topic` | The room topic, if any. Only in `room_topic_change` webhooks.
403
404# Anatomy of an add-on
405
406Here's a closer look at the original example add-on, with comments for illustration:
407
408```
409#!javascript
410
411// Require the 'ac-koa' module, and then tell it to load the 'hipchat' adapter
412// from 'ac-koa-hipchat'
413var ack = require('ac-koa').require('hipchat');
414// Require our package.json file, which doubles as the configuration from which
415// we'll generate the add-on descriptor and server's runtime parameters
416var pkg = require('./package.json');
417// Create the base Koa app, via an 'ac-koa' factory method that helps preconfigure
418// and decorate the app object
419var app = ack(pkg);
420
421// Now build and mount an AC add-on on the Koa app; we can either pass a full or
422// partial descriptor object to the 'addon()' method, or when we provide none, as
423// in this example, we can instead create the descriptor using a product-specific
424// builder API
425var addon = app.addon()
426 // Use the hipchat descriptor builder
427 .hipchat()
428 // Indicate that the descriptor should mark this as installable in rooms
429 .allowRoom(true)
430 // Provide the list of permissions scopes the add-on requires
431 .scopes('send_notification');
432
433// Subscribe to the 'room_enter' webhook, and provide an event listener. Under
434// the covers, this adds a webhook entry to the add-on descriptor, mounts a common
435// webhook endpoint on the Koa app, and brokers webhook POST requests to the event
436// listener as appropriate
437addon.webhook('room_enter', function *() {
438 // 'this' is a Koa context object, containing standard Koa request and response
439 // contextual information as well as hipchat-specific models and services that
440 // make handling the webhook as simple as possible
441 yield this.roomClient.sendNotification('Hi, ' + this.sender.name + '!');
442});
443
444// Now that the descriptor has been defined along with a useful webhook handler,
445// start the server
446app.listen();
447```
448
449# Production deployment using a PaaS
450
451__TODO__