TL;DR

WhatAcDart is an Atlassian Connect client framework written in Dart.

It is designed so that it can be used in an À la carte fashion where you can pick and choose the parts you want in your own Dart stack, at the same time as providing a nice out of the box web stack to get started quickly.

 

Intro

This blog serves two purposes:

  1. Introduce AcDart for anyone that may want to try it
  2. Give an insight into what’s involved in building your own connect client framework

 

Disclaimer

AcDart is not officially supported by the connect team. Currently the officially supported frameworks are:

Having said that I intend to support it and PR’s (well tested of course (wink)) are welcome

Why Dart?

I’ve been using Dart at home for a while now and have grown quite fond of it. The following are some of the features I like:

  1. Dev Speed: It gives me the dev speed of something like Node but with a more tool-able language.
    So I get all the goodies I’m used to like:

    1. useful autocomplete
    2. refactoring (when it works)
    3. and an editor that tells me (mostly) when I stuffed up my code
  2. Modern Language: It’s got many of the modern language features like:
    1. closures & functions
    2. modules & package management
    3. async io, futures & streams
    4. good collection libs (with the usual map, fold, filter, etc. methods)
    5. none of the funky bits of JavaScript
  3. Native on Client: Like JavaScript it will one day (within the next year hopefully) run natively in the browser
    1. well at least in Chrome once Google fixes the garbage collector
    2. It also compiles to JavaScript (obviously) for browsers that don’t have a native VM
  4. On the Server: Google are starting to show it some love on the server now by:
    1. cranking up the performance of its http stack
    2. and building support for it in their Google Cloud Managed VMs (you can already run it on Heroku).

My Happy Place

So in short, I chose Dart because I can use the same language (one I actually like) on both client and server, which is something I missed from the ol’ fat client days.
Yes, in case you are wondering, I don’t have a lot of love for JavaScript, although I have to admit it is much better than it used to be.

DIY Connect Client Framework

So I wanna build my own connect framework, what do I need?

Of course there are many ways to slice things up but these are at least some of the main things you’ll need to address.

AcDart is divided up this way, with each piece being a separate project in pub (Dart’s version of NPM). This allows you to pick and choose the bits you want, assuming of course you don’t want to the whole thing.

 

 

Capability 1: JWT Support

Addons need to be able to:

  1. Authenticate requests from Atlassian Products
  2. Sign requests to Atlassian Products

In addition, many addons will need to be able to:

  1. Authenticate requests from their own client iFrame
  2. Create tokens to support this authentication

Atlassian Product Authentication

This connect uses JWT with an additional claim:

The first thing you’ll need is a JWT library and support for the qsh

 

Addon Session Tokens

For this you can use whatever you want but, if you already have JWT you may as well use that.

All you need is an additional claim:

  • Product Host Key. The issuer is now the addon host so this is an extra field
Second thing you’ll need is a way to create and validate session tokens

AcDart Jwt

Decode and Validate
[cc lang=”javascript” line_numbers=”0″]
JsonWebToken jwt = decodeProductHostToken(jwtStr);
jwt.validate(new JwtClaimSetValidationContext()); // validate all claims
String qsh = jwt.claimSet.queryStringHash; // the qsh if you need it
[/cc]
Create, Sign and Encode
[cc lang=”javascript” line_numbers=”0″]
String jwtToken = createProductHostToken(‘/some/host/path’, ‘GET’,
{ ‘param1’: ‘value1’ }, sharedSecret, hostKey);
[/cc]
AcDart uses Jwt for the session tokens. So similarly
[cc lang=”javascript” line_numbers=”0″]
JsonWebToken jwt = decodeAddonSessionToken(jwtStr);
// etc
[/cc]
[cc lang=”javascript” line_numbers=”0″]
String jwtToken = createAddonSessionToken(addonSecret, addonKey,
productHostKey, user);
[/cc]

 

 

Capability 2: Atlassian Product Interaction

Unsurprisingly, a fair bit of what an addon needs to do is to interact with an Atlassian product instance (like Jira or Confluence). Some of the things you’ll need to support are:

  • automatically installing the addon on startup (dev mode only)
  • handling product host installation lifecycle events
  • signing (jwt) requests to the host
  • persisting host registration information
  • authenticating requests from an Atlassian host
  • creating addon session tokens (based on information from host request)
  • authenticating addon session tokens
The next thing you’ll need is to handle all the host interaction

AcDart Host

Create a Repo

e.g. a mongo one

[cc lang=”javascript” line_numbers=”0″]final repo = new AcHostRegistrationMongoRepository(db);[/cc]

Create the AcHostService object

[cc lang=”javascript” line_numbers=”0″]final hostService = new AcHostService(repo, addonHost);[/cc]

Handle the Installation Event by Registering the Host

[cc lang=”javascript” line_numbers=”0″]hostService.registerHost(installationEvent);[/cc]

Send an HTTP Request to a Host

e.g. talk to Jira

[cc lang=”javascript” line_numbers=”0″]
final jiraResponse = hostService.httpClientForHostKey(hostKey)
.then((httpClient) => httpClient.get(‘rest/api/2/issue/$issueKey’,
signRequest: true))
.then((req) => req.close());

jiraResponse
.then(HttpBodyHandler.processResponse)
.then((body) {
Map issueJson = body.body; // do something with the issue json
});

[/cc]

 

 

Capability 3: Configuration

Every addon will need to operate in different environments such as:

  • Local development
  • Development & Staging on some cloud provider like Heroku
  • Production

To facilitate this, it’s useful to have support for managing these different configurations.

The next thing you’ll need is configuration support

AcDart Config

AcDart deviates from the norm of storing configuration in text files (properties, YAML, JSON etc.).

The rationale is:

  1. Dart is not compiled like Java and packaged into a versioned artefact like a WAR file so any config lives with the code anyway
  2. You can well and truly screw up an application by screwing up the config, so keeping config in text doesn’t confer some magical safeguard
  3. All the properties that may change across environments are exposed as environment variables
  4. Dart’s syntax enables it to look similar to a JSON file
  5. The editor (and analysers) will give you autocomplete and warnings when you make a typo, making it easier to create the config and get it right

 

[cc lang=”javascript” line_numbers=”0″]
final config = new AcDartConfigFactory.standard(
new AcDartConfig(
addonKey: ‘foo-bar-addon’,
repoConfig: new AcDartHostRepoConfig.mongo()
),
development: () => new AcDartConfig.development(
installAddonOnStartup: false
),
production: () => new AcDartConfig());
[/cc]

Capability 4: A Web Stack

Finally you need an actual web stack and server so you can serve the request from the host and addon client.

This will make use of all the above components and will add things like:

  • A request router
  • Filters / Middleware to handle logging, authentication etc.
  • Template engines if server side templating is your thing
  • etc.
Lastly, you need a web stack

AcDart Shelf

AcDart uses a very new framework called Shelf. This is just a barebones framework for adding any middleware components / handlers etc.

Shelf is being pushed by the core Dart team and has a lot of potential.

The catch is that, as with all early adoption, you run the risk of needing to write stuff yourself. In my case I had to write a router.

Set up Your Routes

[cc lang=”javascript” line_numbers=”0″]
void initRoutes(AcDartMainRouteBuilder routeBuilder,
String addonKey, Uri addonBaseUri) {routeBuilder.addInstallationRoute();
routeBuilder.addDescriptorTemplateRoute();

routeBuilder.addJwtRoute(
m.moustacheFile(‘../ui/exampleUI.html’, includeExtraParams: true),
‘/ui/exampleUI.html’, method: ‘GET’);

routeBuilder.addHostServiceSessionTokenRoute(_fetchJiraIssue,
‘/issue/:$_ISSUE_KEY’);
}
[/cc]

Fire up the server

[cc lang=”javascript” line_numbers=”0″]
void main() {
startServer(initRoutes, config);
}
[/cc]

 

 

Summary

So as you can see, it’s a moderate amount of work to create a new Atlassian Connect client framework.

Not something you are likely to knock up in a new language in a day, but you’ll likely have one in a week or two if you roll your sleeves up.

If what you are after are just minor tweaks to existing frameworks, then consider a pull request on the appropriate project.

If you have a play with AcDart let me know what you think.

AcDart – An Atlassian Connect Client Framework in Dart