Countly Documentation

Countly Resources

Here you'll find comprehensive guides to help you start working with Countly as quickly as possible.

Frontend server side files

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;

Frontend server side files