umop apisdn
umop apisdn

Reputation: 683

Pass data to client-side JavaScript (preferably without view engine or cookies)

Part I: Problem introduction

The short story: I need to pass data from the server to the client, but this data will not need to be displayed on the client end. Instead, the data is used by the client socket to join the correct room. Move on to Part II: Solution attempt

The long story:

For my first web development project, I am attempting to create a remote presenter (i.e. using a mobile device to control a Google Slides presentation on desktop).

On the frontend, I have been using plain old HTMl, CSS and vanilla JS, while the backend has involved NodeJS, Express framework and Socket.io.

Here's a brief overview of the user flow: when a user is authenticated via Google sign-in on the desktop, the website will open a socket connection which is automatically joined to a room identified by the socket id. After the user has signed in on the desktop end, the user will (for now) see this socket id as a 'secret key' which he can use to login on the mobile end.

Hence, I have a server.js which handles the routing and form action (i.e. the secret key submitted on the mobile end) like so:

// Store all the currently connected web clients
var webClients = [];

// Index route -- home page for website and mobile site
app.get('/', function (req, res) {
  // Basic user agent check - test for mobiles
  var userAgent = req.headers['user-agent'];
  if (/mobile/i.test(userAgent)) {
      // Send mobile to the mobile login site
      res.sendFile(__dirname + '/pages/mobile.html');
  } else {
      // Send desktop to the main site
      res.sendFile(__dirname + '/pages/index.html');
  }
});

// Dealing with secret key input
app.post('/secretKey', function(req, res) {

    // Store the secret key in a variable first
    var secretKey = req.body.secretKey;

    // Check if the secret key matches with any key in the database (current session)
    var index = webClients.indexOf(secretKey);

    // Send the user to the mobile controls if there is something that matches
    if (index != -1) {
        res.sendFile(__dirname + '/pages/mobileControl.html');
    } else {
        res.redirect('/');
    }
  }

});

Unfortunately, I have hit a snag. After the mobile controls page is loaded, another socket instance will be opened on the mobile end. In order to ensure that the mobile is controlling the right presentation, I need to let the socket on the mobile end join the same room as the desktop socket (i.e. the room with the desktop socket's id). Hence, I need to pass the desktop socket Id to the mobile side, so that the mobile socket can connect to the right room.

Part II: Solution attempt

I know that several other users have already asked similar questions on SO. For example:

From these, I can summarise several prominent recommendations:

  1. Using a template system such as Jade, ejs etc

I looked into Jade and ejs, and I am reluctant to use these template systems because I suspect that they might be overkill for what I am trying to achieve here - simply pass a string from the server to client side JavaScript without rendering the string in the view. Additionally, I do not really need the partials offered by these template systems, further reinforcing my belief that these may be overkill.

  1. Storing data in a cookie, and accessing the cookie on client-side JavaScript

Currently, this solution appeals the most to me simply because it seems to be the easy way out. I can simply do something like:

var options = {
     headers: {
          'set-cookie' : roomId=secretKey
     }
  };
res.sendFile(__dirname + '/pages/mobileControl.html', options);

Then, I can access the data in the cookie within mobileControl.js by doing something like document.cookie. However, I have read that there are security issues associated with this method, such as cross-site scripting (XSS) attacks. Hence, I am reluctant to use this method as well.

  1. Send the data to another route, and use an XMLHttpRequest to retrieve the data after rendering the html

Aside from these three methods, I have also looked into implementing user sessions, but I do not think that that is the solution for me since user sessions will likely come in at a later stage to allow for logins to persist.

So, which method do you think I should use in this case? Should I just go along with 2, and tweak the HttpOnly field of the cookie? Any other recommendations? Thanks a lot!

Upvotes: 3

Views: 2325

Answers (1)

Laura
Laura

Reputation: 3373

If you don't want to use a full blown template engine (even though I think this would not be over the top for this - that's what template engines are for), how about just doing a single string replace?

Put something like

<script>var SECRET_KEY = '{{{SECRET_KEY}}}';</script>

in your HTML file, stream the file instead of sending it and use something like stream-replace to replace the {{{SECRET_KEY}}} string with your secret key.

Untested example:

var fs = require('fs');
var replace = require('stream-replace');

app.post('/secretKey', function(req, res) {

    // Store the secret key in a variable first
    var secretKey = req.body.secretKey;

    // Check if the secret key matches with any key in the database (current session)
    var index = webClients.indexOf(secretKey);

    // Send the user to the mobile controls if there is something that matches
    if (index != -1) {
        fs.createReadStream(__dirname + '/pages/mobileControl.html')
            .pipe(replace('{{{SECRET_KEY}}}', secretKey))
            .pipe(res);
    } else {
        res.redirect('/');
    }
});

Then you'll be able to use the SECRET_KEY variable in your script.

Upvotes: 2

Related Questions