Frontend Server Side Files

Follow

Since on the server side for frontend Countly uses Express.js adding new functionality is extremely easy.

And with EJS templating you can easily pass data to your templates and render it.

File that will handle frontend requests should be named app.js and located in your plugins directory api folder as {plugin}/frontend/app.js

Your app.js needs to export object with single public method init, to which Countly will pass database connection, express app instance and express reference, so you could add any request handling or middleware as you want.

Countly path prefix

Countly might be installed in subdirectory of a domain, in that case countlyConfig will have a path property defined. You need to consider it when registering request pass with express app

Example app.js file could look like:

var plugin = {},
    countlyConfig = require('../../../frontend/express/config');

(function(plugin) {
    plugin.init = function(app, countlyDb, express) {

        //add your middleware or process requests here
        app.get(countlyConfig.path + '/ourplugin', function(req, res, next) {

            //get url parameters
            var parts = req.url.split("/");
            var id = parts[parts.length - 1];

            //read data from db using countlyDB
            countlyDb.collection('ourplugin').findOne({
                '_id': id
            }, function(err, plugindata) {

                //if no data available
                if (err || !att) res.send('404: Page not Found', 404);
                else {

                    //render template with data	
                    res.render('../../../plugins/ourplugin/frontend/public/templates/default', {
                        path: countlyConfig.path || "",
                        cdn: countlyConfig.cdn || "",
                        data: plugindata
                    });
                }
            });
        });
    };
}(plugin));

module.exports = plugin;

This example will render a template located at {countly}/plugins/ourplugin/frontend/public/templates/default.html which should be an EJS template where you can process and display prepopulated plugin data.

Intercepting requests

Request registration to plugins are passed before any other request is registered by the core, meaning any request will be firstly passed to plugin allowing you to either override it or modify it.

And in some cases you would want to intercept request made to core either to perform some additional tasks based on request data or to modify request, etc. For that purpose you can register request with express app and use next function to pass request processing to Countly core.

//add your middleware or process requests here
app.get(countlyConfig.path+'/login', function(req, res, next) {
      
  //do something with request data
      
  //let Countly handle natural login flow
  next();
  
});

Changing Countly configurations

Some Countly configurations are used in frontend and are even passed to browser side and used in templates.

You can change them in your plugin. Each req request object additionally contains config property with Countly's frontend/express/config.js contents that you can modify for each specific request separately

Checking if user is authenticated

Some pages should be accessed only by authenticated users. To check if user is authenticated, simply check if he has any session uid defined.

Additionally you might want to check if user is global admin, by checking sessions gadm property

app.get(countlyConfig.path+'/mypage', function (req, res, next) {
  
  //check if user is authenticated
  if (req.session.uid) {
    
    if(req.session.gadm){
      //user is global admin
    }
    else{
      //user is simple user
    }
  }
  else
    //redirect to login page
  	res.redirect(countlyConfig.path+'/login');
});

Handling POST requests

Countly uses body parser middleware by default, so handling post requests is as easy as get requests

app.post(countlyConfig.path+'/mypage', function (req, res, next) {
  
  //data we received with post
  console.log(req.body)
  
  //render something or redirect user here
  res.end();
  return true;
});

File uploads are as easy to handle

app.post(countlyConfig.path+'/mypage', function (req, res) {
  
  //your file data is located at req.files.fieldname
  var tmp_path = req.files.fieldname.path,
      target_path = __dirname + "/public/images/image.png",
      type = req.files.app_image.type;

  // lets check file type
  if (type != "image/png" && type != "image/gif" && type != "image/jpeg") {
    
    //nothing that we would want, delete file
    fs.unlink(tmp_path, function () {});
    res.send(false);
    return true;
  }

  //else we have an image, lets copy it
  fs.rename(tmp_path, target_path, function (err) {
    
    //and remove uploaded file
  	fs.unlink(tmp_path, function () {});
		
    //output that we are finished
    res.send("uploaded");
  });
});

Static paths

In most cases when creating UI for your plugin, you would have your plugin specific css and javascript files, as well as templates that should be accessible from the web to a frontend client.

Countly core automatically adds your plugins public directories to static path list thus everything that is located at {countly}/plugins/yourplugin/frontend/public directory will be automatically available for whole world to see.

These public resources could be accessed in your templates or loaded via javascript by using your plugin name as subpath.

So if you want to access css file which is located at {countly}/plugins/yourplugin/frontend/public/stylesheets/main.css you would have to use url: http://yourdomain.com/yourplugin/stylesheets/main.css

Additionally you can specify your own static paths if needed using staticPaths method on your frontend plugin.

You can use the same method to override some existing static files, like redirect main css request to some other themed css file.

Note that you should use app.use inside staticPaths method, because it is invoked before router middleware

var plugin = {},
    countlyConfig = require('../../../frontend/express/config');

(function (plugin) {
	plugin.staticPaths = function(app, countlyDb, express){
        //redirect static file path to another
        app.use(countlyConfig.path+'/stylesheets/main.css', function (req, res, next) {
            res.redirect(countlyConfig.path+'/ourplugin/stylesheets/main.css');
				});
        
        //add your own new static paths
        app.use(countlyConfig.path+"/myfolder", express.static(__dirname + "/myfolder"));
    };
}(plugin));

module.exports = plugin;

Frontend methods

Most of the cases can be handled by Express.js methods of intercepting requests before Countly core processes it. But in some cases you might want to get notified about some specific actions or do changes inside Countly flows.

For example, you don't want to listen to all /login calls, but you may need to know when user successfully logged in. Or you may want to modify javascript object which is exposed to dashboard or passed to EJS template. Or you might want to cancel CSRF for some frontend requests.

To accomplish that you can define methods for frontend part of your plugin object, that will be called in some specific cases.

Each method gets object with:

  • req - request object
  • res - response object
  • next - express js next callback
  • data - some additional info
Method name When it is called Notes
staticPaths On server process start before assigning static path files Can be used to add additional static paths if needed, or overwrite existing static paths
skipCSRF On each request before performing CSRF check Return false if you want to skip CSRF check for this request
userLogout When user successfully logged out Contains uid and email in data
renderDashboard When dashboard is rendered Data contains: member - user data adminApps - id of apps user is admin of userApps - id of apps user is user of countlyGlobal - global object ot be exposed in browser toDashboard - object to be passed to EJS template
passwordRequest When user requested to change password Data contains: email of the user requested
passwordReset When user resseted password Data contains: member document
setup When first user created account when setting up Countly Data contains: member document
loginSuccessful When user successfully logged in Data contains: member document
loginFailed When user login failed Data contains: username password
apikeySuccessful When user requested api key Data contains: member document
apikeyFailed When user requested api key, but authentication failed Data contains: username
mobileloginSuccessful When user logs in through mobile Data contains: member document
mobileloginFailed When authentication fails through mobile Data contains: username
iconUpload When user uploads app icon Data contains: app_image_id
userSettings When user changes user settings Data contains: member document

Example code of using frontend methods

var plugin = {},
    countlyConfig = require('../../../frontend/express/config');

(function (plugin) {
		plugin.init = function(app, countlyDb){
        //add new request handles here as expected
        app.post(countlyConfig.path+'/nocsrf', function (req, res, next) {
        	res.write(true);
        })
		};
  
  	plugin.staticPaths = function(app, countlyDb, express){
        //redirect static file path to another
        app.use(countlyConfig.path+'/stylesheets/main.css', function (req, res, next) {
            res.redirect(countlyConfig.path+'/ourplugin/stylesheets/main.css');
				});
        
        //add your own new static paths
        app.use(countlyConfig.path+"/myfolder", express.static(__dirname + "/myfolder"));
    };
    
    //let's skip csrf for our nocsrf request
    plugin.skipCSRF = function(ob){
        if(ob.req.path == countlyConfig.path+"/nocsrf")
            return true;
        return false;
    };
    
    //let's add additional object to expose on dashboard
    plugin.renderDashboard = function(ob){
    		//this can be accessed on browser side as
        //countlyGlobal.myValue
        ob.data.countlyGlobal.myValue = 42;
    }
}(plugin));

module.exports = plugin;

Looking for help?