Mobile Device Detection and Redirection in Node+Express
One of the challenges of writing a mobile web site is what to do if a user hits the site from a desktop? Of course, some will say that you should simply make the site responsive, able to look good regardless of the browse it is displayed on. But what if the site is a game? I know all of the responsive design true believers will be upset with my solution, but so what, I like it. I "iframe" the mobile site into a 320 x 480 window on the desktop page. This brings up more challenges, like how detect a mobile device and how to route to the correct page?
The first challenge has already been solved by the excellent web site: http://detectmobilebrowsers.com/. They have created a rather long regular expressions which detects a mobile browser by its user agent. The answer to the second challenge comes from the Express.js middleware which makes Node.js a great framework.
Express allows the developer to inject middleware into the route handler. For our root route we will call the checkForMobile method. If we detect a mobile browser, we will redirect to our mobile route. On all other routes, we won't bother checking for mobile. If a desktop user wishes to navigate directly to the mobile page, we will let them.
Please check out the code sample below to see how it is done. Or if your are interested in the complete, and still in progress, mobile game check it out at: https://github.com/Rockncoder/planegame. To see the live and very much in progress code, go to: http://planegame.herokuapp.com/
I will be talking about the complete game at So Cal Code Camp at USC, the weekend of November 9th & 10th. If you are in the area, be sure to attend. It is a free event: http://www.socalcodecamp.com/
Please check out the code sample below to see how it is done. Or if your are interested in the complete, and still in progress, mobile game check it out at: https://github.com/Rockncoder/planegame. To see the live and very much in progress code, go to: http://planegame.herokuapp.com/
I will be talking about the complete game at So Cal Code Camp at USC, the weekend of November 9th & 10th. If you are in the area, be sure to attend. It is a free event: http://www.socalcodecamp.com/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Module dependencies. | |
*/ | |
var express = require('express') | |
, routes = require('./routes') | |
, http = require('http') | |
, hbs = require('hbs') | |
, path = require('path') | |
, app = express(); | |
app.configure(function () { | |
app.set('port', process.env.PORT || 3000); | |
app.set('views', __dirname + '/views'); | |
app.set('view engine', 'hbs'); | |
app.use(express.favicon()); | |
app.use(express.logger('dev')); | |
app.use(express.bodyParser()); | |
app.use(express.methodOverride()); | |
app.use(app.router); | |
app.use(express.static(path.join(__dirname, 'public'))); | |
}); | |
app.configure('development', function () { | |
app.use(express.errorHandler()); | |
}); | |
// when the root route is called, do our mobile check | |
app.get('/', checkForMobile, routes.desktop); | |
app.get('/mobile', routes.mobile); | |
http.createServer(app).listen(app.get('port'), function () { | |
console.log("Express server listening on port " + app.get('port')); | |
}); | |
// returns true if the caller is a mobile phone (not tablet) | |
// compares the user agent of the caller against a regex | |
// This regex comes from http://detectmobilebrowsers.com/ | |
function isCallerMobile(req) { | |
var ua = req.headers['user-agent'].toLowerCase(), | |
isMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(ua) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(ua.substr(0, 4)); | |
return !!isMobile; | |
} | |
// note: the next method param is passed as well | |
function checkForMobile(req, res, next) { | |
// check to see if the caller is a mobile device | |
var isMobile = isCallerMobile(req); | |
if (isMobile) { | |
console.log("Going mobile"); | |
res.redirect('/mobile'); | |
} else { | |
// if we didn't detect mobile, call the next method, which will eventually call the desktop route | |
return next(); | |
} | |
} |