3

I'm trying to create a webapp for a web art class using node (w/ npm) and express. The idea is to have the body of the site be all one color, but anyone can text the site a hexcode/CSS color at a Twilio number and the color of the site will instantly change to that color value.

Essentially how it works is the server receives a POST request from Twilio at http://example.com/message, which contains the body of the text message. It writes it to a temporary file at ~/app/.data/color.tmp, which is accessed by the client with a jQuery .get() call to http://example.com/color, which returns

So here's the problem: I got a version of the app working on glitch.me, so I know that this code can work, but I'm having a lot of trouble getting it to work on my domain. I installed the app and can start it with npm, and it successfully shows me the HTML page, but the Chrome devtools show the script is receiving a 403 when it tries to access /color. Also, new texts to my site aren't changing the color value in /.data/color.tmp. I thought it might be a permissions issue but I checked them and they seem fine.

Here's the server file and the script on the index.html page:

app/server.js

var express = require('express');
var bodyParser = require('body-parser');
var fs = require('fs');
var app = express();
app.use(bodyParser.urlencoded({extended: false})); 
var dataPath = '.data/color.tmp';


// set a new color (saves posted color to disk)
app.post("/message", function (request, response) {
  var dataStr = JSON.stringify(request.body.Body);
  fs.writeFile(dataPath, dataStr);
  response.end();
});

// get the saved color (reading from disk)
app.get("/color", function (request, response) {
  var dataStr = fs.readFileSync(dataPath).toString();
  response.send(JSON.parse(dataStr));
});

app.get("/", function (request, response) {
  response.sendFile(__dirname + '/views/index.html');
});

var listener = app.listen(process.env.PORT, function () {
  console.log('listening on port ' + listener.address().port);
});

app/views/index.html

    <script>
      // checks server for color value and sets background
      function checkForColorChange() {
        $.get('/color', function getColorComplete(data) {
          document.body.style.backgroundColor = data;
            console.log(data);
        })
      }

      // Poll the server at 2000ms interval
      setInterval(checkForColorChange, 2000);

      checkForColorChange();
    </script>

Anyway, I feel like I must be missing something really obvious if it worked so easily on Glitch and won't on my website, but I've been stuck for a few days and am not making any progress! Any help would be so appreciated. Let me know if anything's unclear too.

wmbevin
  • 39
  • 4
  • 1
    Does the user the app is running as on your server have permission to read/write to `data/color.tmp`? – dan Apr 19 '17 at 15:20
  • @dan yes I just checked, it's running through a screen on my user acct which should have full r/w permissions. http://imgur.com/a/FqFE4 – wmbevin Apr 19 '17 at 15:36
  • @wombevyn Have you seen the updates to [my answer](http://stackoverflow.com/questions/43499382/getting-data-from-writing-data-to-localhost-with-express/43500104#43500104) below? It includes a working example of what you wanted to achieve here, with [source code on GitHub](https://github.com/rsp/node-live-color) and [Deploy to Heroku](https://heroku.com/deploy?template=https://github.com/rsp/node-live-color) option for quick testing. I wonder if you used it in your demonstration and if you have any comments about it. – rsp Apr 28 '17 at 09:02

2 Answers2

1

(See update below for a working example)

TL;DR - example:
Deploy to Heroku

Original answer

There are few problems with your code:

  1. you're not checking for errors
  2. you're using blocking functions
  3. you're implicitly relying on file permissions but you're not checking it
  4. you're using string concatenation instead of path.join to join paths
  5. you're constantly polling for new data instead of waiting for it to change
  6. you're not catching exceptions of functions that can raise exception
  7. you're not waiting for async operations to finish and you don't handle errors

The main problem that you're experiencing right now is most likely with the file permissions. The good news is that you don't need any file access for what you're doing and using files for that is not optimal anyway. All you need is to store the color in a variable if you don't need it it persist between server restarts - and even if you do then I would use a simple database for that.

For example:

// some initial value:
var color = '#ffffff';

app.post("/message", function (request, response) {
  var color = request.body.Body;
  response.end();
});

// get the saved color (reading from disk)
app.get("/color", function (request, response) {
  response.send(color);
});

app.get("/", function (request, response) {
  response.sendFile(__dirname + '/views/index.html');
});

var listener = app.listen(process.env.PORT, function () {
  console.log('listening on port ' + listener.address().port);
});

This is the first change that I would use - don't rely on the file system, permissions, race conditions etc.

Another problem that you had with your code was using blocking functions inside of request handlers. You should never use any blocking function (those with "Sync" in their name) except the first tick of the event loop.

Another improvement that I would make would be using WebSocket or Socket.io instead of polling for data on regular intervals. This would be quite easy to code. See this answer for examples:

A plus of doing that would be that all of your students would get the color changed instantly and at the same time instead of in random moments spanning 2 seconds.

Update

I wrote an example of what I was describing above.

The POST endpoint is slightly different - it uses /color route and color=#abcdef instead of /message and Body=... but you can easily change it if you want - see below.

Server code - server.js:

// requires removed for brevity

const app = express();
const server = http.Server(app);
const io = socket(server);

let color = '#ffffff';

app.use(bodyParser.urlencoded({ extended: false }));
app.use('/', express.static(path.join(__dirname, 'html')));

io.on('connection', (s) => {
  console.log('Socket.io client connected');
  s.emit('color', color);
});

app.post('/color', (req, res) => {
  color = req.body.color;
  console.log('Changing color to', color);
  io.emit('color', color);
  res.send({ color });
});

server.listen(3338, () => console.log('Listening on 3338'));

HTML page - index.html:

<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1">
<title>Node Live Color</title>
<link href="/style.css" rel=stylesheet>
</head>
<body>
<h1>Node Live Color</h1>
<script src="/socket.io/socket.io.js"></script>
<script src="/script.js"></script>
</body>
</html>

Style sheet - style.css:

body {
  transition: background-color 2s ease;
  background-color: #fff;
}

Client-side JavaScript - script.js:

var s = io();
s.on('color', function (color) {
  document.body.style.backgroundColor = color;
});

What is particularly interesting is how simple is the client side code.

For your original endpoint use this in server.js:

app.post('/message', (req, res) => {
  color = req.body.Body;
  console.log('Changing color to', color);
  io.emit('color', color);
  res.end();
});

Full example is available on GitHub:

I tested it locally and on Heroku. You can click this button to deploy it on Heroku and test yourself:

Deploy to Heroku

Enjoy.

Community
  • 1
  • 1
rsp
  • 107,747
  • 29
  • 201
  • 177
  • Thank you so much for this incredibly detailed feedback, not sure if I'll have time to implement socket.io in the project but I'll take a look and give it a shot. What about the 403 error I keep getting from the client-side script that's trying to access /color? Do I need to have a '/public_html/color' folder or something? – wmbevin Apr 19 '17 at 18:34
  • @wombevyn I wrote a full working example with Socket.io - see my updated answer. – rsp Apr 20 '17 at 23:59
  • @wombevyn Have you seen the updated answer? Does it work for you? Any comments? – rsp May 30 '17 at 15:21
  • it totally works for me! really well done, and really appreciate you taking the time to make it -- I hooked it up to the Twilio number for a bit and sent it some colors from my phone. Also showed this demo in class back when you wrote it, sorry I took so long to respond. – wmbevin Jun 06 '17 at 16:56
0

I think, the problem is in var dataStr = fs.readFileSync(dataPath).toString();. Please change your dataPath as follow:

 var dataPath = __dirname + '/data/color.tmp';

And also make sure that file has read/write permission by the .

Tolsee
  • 1,695
  • 14
  • 23