200 response with HTML fails to render webItems module

I am testing my Bitbucket Cloud add-on and can see that Bitbucket is trying to load my “webItems” module in the UI as well as in the Chrome console. The Chrome console shows that my URL responded with a 200 success and I can see the HTML content as well. However, the Bitbucket UI never populates my content and continues to try to load it.

I’m assuming that there is something wrong with my server’s response - maybe Bitbucket isn’t expecting an HTML response? Any insight would be much appreciated.

If it matters, I’m using localtunnel.me to test my add-on.

Hi @anon11454250,

Welcome to the community! Could you please share your app descriptor and share what you see in your Bitbucket UI and Chrome console?

Cheers,
Anne

@acalantog - Thanks for your attention. Here are the details you requested.

{
  "key": "doc-me-up",
  "name": "Doc Me Up",
  "description": "Does stuff \n",
  "vendor": {
    "name": "Tyler Mills",
    "url": "http:\/\/docmeup.lndo.site"
  },
  "baseUrl": "https:\/\/docmeup.localtunnel.me\/connector\/bitbucket",
  "authentication": {
    "type": "jwt"
  },
  "lifecycle": {
    "installed": "\/installed",
    "uninstalled": "\/uninstalled"
  },
  "modules": {
    "webhooks": [
      {
        "event": "*",
        "url": "\/webhook"
      }
    ],
    "webItems": [
      {
        "url": "http:\/\/example.com?repoPath={repository.full_name}",
        "name": {
          "value": "Example Web Item"
        },
        "location": "org.bitbucket.repository.navigation",
        "key": "example-web-item",
        "params": {
          "auiIcon": "aui-iconfont-link"
        }
      }
    ],
    "repoPages": [
      {
        "url": "\/repo-page\/{repository.full_name}",
        "name": {
          "value": "Example Page YAS"
        },
        "location": "org.bitbucket.repository.navigation",
        "key": "example-repo-page",
        "params": {
          "auiIcon": "aui-iconfont-doc"
        }
      }
    ],
    "webPanels": [
      {
        "url": "\/web-panels\/{repository.full_name}",
        "name": {
          "value": "Example Web Panel NAS"
        },
        "location": "org.bitbucket.repository.overview.informationPanel",
        "key": "example-web-panel"
      }
    ]
  },
  "scopes": [
    "account",
    "repository"
  ],
  "contexts": [
    "account"
  ]
}

I can only upload one image per post, so here is the second:

…and the third:

image

@acalantog - I may have a lead here. It could be that I have not been embedding the JWT token in my iframes since https://developer.atlassian.com/cloud/bitbucket/getting-started/ makes no mention of doing so.

Taking a look at the Atlassian Connect Express docs I learned that Bitbucket is expecting the JWT token to be returned by my server.

I’m building my add-on with Laravel and therefore am calling the token with $request->get('jwt') and have tried adding it as a header (Authorization: JWT {{token}}) and as a meta tag (<meta name="token" content="{{token}}">). Neither of these moved me forward.

Do I need to be building my own JWT token for the response? If so, what data should it be built on?

Sorry for the multiple post. These image and link limitations are killing me. I can’t add anymore links to my previous response, otherwise I would have updated it.

Anyway, it looks like the demo add-on server is building out it’s own JWT token to respond with. Assuming this is why my iframes are not loading, where can I find documentation regarding what the payload should be?

Here is what I have in place currently:

//add-on client secret as shown on https://bitbucket.org/account/user/{USER_NAME}/applications
$key = config('gitconnector.bitbucket.client.secret');

$token = array(
    // Issuer ("iss") matches describer JSON "key" property
    "iss" => config('gitconnector.bitbucket.connector.key'), 
    "iat" => time(),
    "exp" => time() + 60
);

return response()
    ->view('connector.bitbucket.panel')
    ->header(
        'Authorization',
        'JWT ' . \Firebase\JWT\JWT::encode($token, $key, 'HS256')
    );

Hi @anon11454250,

Thanks for the details. Let me see what I can do.

Cheers,
Anne

Hi @anon11454250,

I’m not particularly an expert on Laravel as I mainly develop using the Atlassian Connect Express framework but I’ll try to help you out as much as I can. However, should be agnostic wrt to installation and module registration as you only need to provide an app descriptor and a host.

Few comments on the previous post:

It actually points to your webPanel located at org.bitbucket.repository.overview.informationPanel (basing from your attached screenshot of repository overview). Your web-item can be seen in your side bar :slight_smile:

What do you mean by embedding here? JWT should be automatically added in your iframes. When you inspect your network connection or iframe’s name you should see a query parameter jwt being sent to your app for your own processing and usage

I’m sorry I don’t understand what you mean here

That is not a sample app but the repository for the Atlassian Connect Express framework used by an Atlassian Connect Express app or the scaffolded project atlassian-connect-express-template mentioned in https://developer.atlassian.com/cloud/bitbucket/getting-started/ . But yes, it already process jwt for you and provides a nifty httpClient and helpers.

Since you’re not using Atlassian Connect Express framework, you might be interested in using the PHP framework and tools indicated here from this community post. I think the missing links between the iframe and your router /web-panels\/{repository.full_name} are the OAuth handshake and storage of client information - clientKey, sharedSecret. Although you should be able to render an HTML without the need to authenticate JWT passed from iframe. You may want to try this first to see if your HTML is rendered properly.

Something like this:

app.get('/connect-example', function (req, res) {
    res.render('connect-example-hbs', {
            title: 'Atlassian Connect',
            displayName: 'Anne Calantog',
            repoPath: req.query.repoPath
        });
    });

compared to when you want secure access to your routes:

// This route will be targeted by iframes rendered by Bitbucket. It renders a simple template
    // with two pieces of data:
    //
    //   1. the repository path (passed in the query string via a context parameter)
    //   2. the user who installed the add-on's display name (retrieved from Bitbucket via REST)

    app.get('/connect-example', addon.authenticate(), function (req, res) {

        // the call to addon.authenticate() above verifies the JWT token provided by Bitbucket
        // in the iframe URL

        var httpClient = addon.httpClient(req);

        httpClient.get('/api/1.0/user/', function (err, resp, data) {
            try {
                data = JSON.parse(data);
                res.render('connect-example', {
                    title: 'Atlassian Connect',
                    displayName: data.user.display_name,
                    repoPath: req.query.repoPath
                });
            } catch (e) {
                console.log(e);
                res.sendStatus(500);
            }
        });
    });

The addon variable here was injected and instanstiated in app.js:

var ac = require('atlassian-connect-express');

var addon = ac(app, {
  config: {
    descriptorTransformer: (descriptor, config) => {
      //descriptor.modules.oauthConsumer.clientId = config.consumerKey();
      return descriptor;
    }
  }
});

// Wire up your routes using the express and `atlassian-connect-express` objects
routes(app, addon);

// Boot the damn thing
http.createServer(app).listen(port, function(){
  console.log('Add-on server running at http://' + os.hostname() + ':' + port);
});

which looks like this:

You may also refer to the sample app I created demonstrating JWT OAuth flow but should have the same concept to what you’re currently trying to achieve.

Let me know if that clarifies it or if you have questions and I’ll be happy to help you out.

Cheers,
Anne

@acalantog - thank you for your continued support here.

Right, I am able to validate the token that is coming from Bitbucket. But my takeaway was that Bitbucket was expecting me to embed a token in my source code or in my response header. I tried using both the token that Bitbucket provided as well as a token that I generated based on the link that I shared (quoted above).

In my original post, I mentioned that my add-on is responding with a 200 response and I can see my HTML rendered within the chrome dev tools. However, Bitbucket behaves as if it is still waiting on a response.

To summarize where we are now:

  • My content response to Bitbucket with a 200 and rendered HTML within Chrome Dev Tools. It works both with and without JWT authentication enabled on my server.
  • Bitbucket is not content with the 200 response and behaves as if it is continuing to wait for a response from my server
  • I will test out the Laravel 5 package recommended by Bitbucket as you proposed.

Forgive me for asking too much - if you’re open to it, I can provide my describer URL and a test Bitbucket account if you’re open to taking a closer look.

I’ll report back on my findings after testing the Laravel package.

After digging around the Laravel package a bit I came across the code below. I haven’t found the section of the docs outlining that this is required, but now my config page is loading! I’m guessing it will be required on any page that will be embedded in Bitbucket.

Thanks for sticking around to see me through this.

<script src="//aui-cdn.atlassian.com/aui-adg/6.0.9/js/aui.min.js" data-options="sizeToParent: true"></script>
<script id="connect-loader" data-options="sizeToParent:true;"></script>
<script>
  function getUrlParam(p){var m=(new RegExp(p+'=([^&]*)')).exec(window.location.search);return m?decodeURIComponent(m[1]):''}
  var s=document.createElement('script');
  var b=getUrlParam('xdm_e')+getUrlParam('cp');
  s.src=b+'/atlassian-connect/all.js';
  s.async=false;
  s.setAttribute('data-options',document.getElementById('connect-loader').getAttribute('data-options'));
  document.querySelector('head').appendChild(s)
</script>

Hi @anon11454250,

Great to hear that your config page is now loading! I’m sorry you had to go through this process. The"Bitbucket getting started tutorial" assumes usage of the Atlassian Express framework and that automatically adds the all.js for you.

That snippet you attached injects the all.js enabling your app to use Atlassian Connect’s Javascript APIs. You can also include it:

<script src="https://connect-cdn.atl-paas.net/all.js"></script>

Please don’t hesitate to ask in the community if you have further questions and I’ll be happy to help out.

Happy coding!

Cheers,
Anne