Unable to install App


#1

Hi,

I have create simple app and I am able to load descriptor successfully but when I install app on Stride I am getting following error

We ran into a little trouble
It might just be a hiccup. Try again in a bit.
TID: 785bde582aa67b08, Type: LifecycleCallbackServiceError

on ngrok console it showing as follows

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /installed</pre>
</body>
</html>

i am not sure how to debug this :frowning:

Thanks,


#2

Hi @prasad304,

this error usually occurs when the Stride server can’t reach the /installed callback that your app must provide while being installed.
Are you sure your app is running and reachable and can react to POST /installed HTTPS calls at the address given in the descriptor?

Cheers,
Tobi


#3

How to check this?
i am using ngrok and it’s console showing as follows


#4

From your screenshot, I would say that there is no handler registered under the POST /installed route (i.e. your app is returning a 404 error).

How you define such a route depends on the language and framework you’re using. In Node.js with Express, I would write something like this:

// "app" is the express app or router,
// "validateJwt" is the function that validates JWTs, e.g. the one included in the refapp at https://bitbucket.org/atlassian/stride-apps-reference/src/master/middleware/mw.js
// This assumes that that middleware is also prefilling req.locals for us with the data included in the JWT. The code mentioned above does that.
app.post('/installed', validateJwt, function(req, res) {
	const {cloudId, conversationId} = req.locals;
	console.log(`app was installed in site ${cloudId} room ${conversationId}`);
	res.sendStatus(204);
});
app.post('/uninstalled', validateJwt, function(req, res) {
	const {cloudId, conversationId} = req.locals;
	console.log(`app was uninstalled from site ${cloudId} room ${conversationId}`);
	res.sendStatus(204);
});

The refapp’s code for these lifecycle hooks can be found at https://bitbucket.org/atlassian/stride-apps-reference/src/master/routes/lifecycle.js


#5

I am referring the reference app only and my post handler as follows

router.post('/installed', async function(req, res, next) {
  const loggerInfoName = 'app_install';

  //Send 200 response to Stride immediately, letting the server know you're on it.
  let context = {
    cloudId: req.body.cloudId,
    userId: req.body.userId,
    conversationId: req.body.resourceId,
  };

  let relayState = req.body.relayState;
  if (relayState) logger.info(`${loggerInfoName} The app installation relayState is ${relayState}`);

  try {
    //  Here is where you can store state to your db //
    //...

    // We want to send a welcome message that includes a mention for the app's bot user
    // So first, we'll get the app user Id using the "/me" endpoint
    const appUser = await stride.api.users.me();
    const appUserId = appUser.account_id;

    //Then send a welcome message
    let welcomeDocument = helpers.format.welcomeMessage(appUserId);
    let opts = {
      body: welcomeDocument,
      headers: {
        'Content-Type': 'application/json',
        accept: 'application/json',
      },
    };
    await stride.api.messages.sendMessage(context.cloudId, context.conversationId, opts);

    res.sendStatus(200);
  } catch (err) {
    logger.error(`message_conversation_post error: ${err}`);
    next(err);
  }
});

#6

Hmmm, I’ve just tried out the most recent version of the refapp and it works for me. However, I noticed the path of the installed callback is /lifecycle/installed for me, while it seems to be /installed for you. Did you maybe change anything or have an older version of the refapp?


#7

Could you post the descriptor file you’re hosting? @tobitheo is correct that the reference app endpoint for the lifecycle event should be /lifecycle/installed. The descriptor is being created in this module - https://bitbucket.org/atlassian/stride-apps-reference/src/master/createDescriptor.js .


#8

I have moved descriptor code to lifecycle.js file

const helpers = require('../helpers');
const express = require('express');
const router = express.Router();
const logger = require('../middleware/logger').logger;
const stride = require('../client');


/**
 *  @name Lifecycle: installation events
 *  @see {@link https://developer.atlassian.com/cloud/stride/blocks/app-lifecycle/ | Installation Events }
 *  @description
 *
 *  In order to be granted access to a conversation (for instance, to send messages) an app must be installed by a user in the conversation.
 *  Your app can be notified whenever a user installs or uninstalls it in a conversation. Stride makes a POST request that will be made to the lifecycle URL defined in the app descriptor.
 *  Lifecycle events behave essentially like webhooks.
 **/
router.post('/installed', async function(req, res, next) {
  const loggerInfoName = 'app_install';

  //Send 200 response to Stride immediately, letting the server know you're on it.
  let context = {
    cloudId: req.body.cloudId,
    userId: req.body.userId,
    conversationId: req.body.resourceId,
  };

  let relayState = req.body.relayState;
  if (relayState) logger.info(`${loggerInfoName} The app installation relayState is ${relayState}`);

  try {
    //  Here is where you can store state to your db //
    //...

    // We want to send a welcome message that includes a mention for the app's bot user
    // So first, we'll get the app user Id using the "/me" endpoint
    const appUser = await stride.api.users.me();
    const appUserId = appUser.account_id;

    //Then send a welcome message
    let welcomeDocument = helpers.format.welcomeMessage(appUserId);
    let opts = {
      body: welcomeDocument,
      headers: {
        'Content-Type': 'application/json',
        accept: 'application/json',
      },
    };
    await stride.api.messages.sendMessage(context.cloudId, context.conversationId, opts);

    res.sendStatus(200);
  } catch (err) {
    logger.error(`message_conversation_post error: ${err}`);
    next(err);
  }
});

/**
 *  @name Lifecycle: app descriptor
 *  @see {@link https://developer.atlassian.com/cloud/stride/blocks/app-descriptor/ | Descriptor Requests }
 *  @see {@link https://developer.atlassian.com/cloud/stride/blocks/app-lifecycle/ | Lifecycle Events }
 *  @description
 *
 *  The app descriptor is a JSON file that describes how Stride should communicate with the app.
 *  The descriptor includes general information for the app, as well as the modules that the app wants to use.
 *  The app descriptor serves as the glue between the app and Stride. When a user installs an app,
 *  what they are really doing is installing this descriptor file.
 *
 *  Stride needs to be able to retrieve this from your app server.
 **/

router.get("/descriptor", function(req, res) {
    res.json({
      baseUrl: `https://${req.headers.host}`,
      key: "acis-demo-app",
      lifecycle: {
        installed: "/installed",
        uninstalled: "/uninstalled"
      },
      modules: {
        'chat:inputAction': [
            {
              key: 'inputAction-openDialog',
              name: {
                value: 'Open dialog',
              },
              target: 'actionTarget-sendToDialog',
            },
          ],
          'chat:dialog': [
            {
              key: 'dialog-1',
              title: {
                value: 'App Dialog',
              },
              options: {
                size: {
                  width: '500px',
                  height: '500px',
                },
                primaryAction: {
                  key: 'dialogAction-openSidebar',
                  name: {
                    value: 'Open Sidebar',
                  },
                },
                secondaryActions: [
                  {
                    key: 'dialogAction-closeDialog',
                    name: {
                      value: 'Close',
                    },
                  },
                  {
                    key: 'dialogAction-disableButton',
                    name: {
                      value: 'Disable Button',
                    },
                  },
                ],
              },
              url: '/dialogs/dialog',
              authentication: 'jwt',
            },
            {
              key: 'dialog-configuration',
              title: {
                value: 'App Configuration',
              },
              options: {
                size: {
                  width: '700px',
                  height: '500px',
                },
                primaryAction: {
                  key: 'dialogAction-saveConfiguration',
                  name: {
                    value: 'Save',
                  },
                },
                secondaryActions: [
                  {
                    key: 'dialogAction-closeConfiguration',
                    name: {
                      value: 'Close',
                    },
                  },
                ],
              },
              url: '/configurations/config',
              authentication: 'jwt',
            },
          ],
      }
    });
  });


module.exports = router;

#9

Could you change those to:

      lifecycle: {
        installed: "/lifecycle/installed",
        uninstalled: "/lifecycle/uninstalled"
      },

than refresh the descriptor in your app’s entry on https://developer.atlassian.com/apps and see if that works?


#10

Thanks a lot! it worked.
why we need to mention “/lifecycle/installed” when descriptor is there on same file?


#11

The routes in the descriptor file are relative to the "baseUrl" property, not where the descriptor is hosted. :slight_smile: - App Descriptor documentation


#12

got it, Thank you for your prompt response!