├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── index.js └── package.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pd 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # test 40 | test 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ryan Lindskog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # modernizr-server 2 | Express middleware that exposes client modernizr data to the server. 3 | 4 | # Install 5 | 6 | npm install --save modernizr-server 7 | 8 | # Simple Example 9 | // setup 10 | npm install --save modernizr-server express cookie-praser 11 | 12 | // server.js 13 | const express = require('express') 14 | const cookieParser = require('cookie-parser') 15 | const modernizrServer = require('modernizr-server') 16 | 17 | const app = express() 18 | 19 | app.use(cookieParser()) 20 | app.use(modernizrServer()) 21 | 22 | app.get('/', function(req, res) { 23 | let Modernizr = req.cookies.modernizr 24 | res.send(Modernizr) 25 | } 26 | app.listen(8000, function(err) { 27 | console.log('listening at http://localhost:8000/') 28 | }) 29 | 30 | # Options 31 | modernizr-server supports an options object as a parameter. (See an example below). 32 | 33 | **storageMethod**: String 34 | 35 | Default: 'cookie' 36 | 37 | If you want to use a session, set this option as 'session'. See 'Example (session)' below. modernizr-server by default stores the modernizr information as a cookie. This is good if you want to access the same modernizr on the client; however, modernizr builds can be pretty big. If you want to use this method, we recommend that you use a smaller build. We also support 'session' as a storage method. This is the preferred method. 38 | 39 | **cdn**: String 40 | 41 | Default: 'https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js' 42 | 43 | If no build options are given ('config' or 'build'), modernizr-server will use a [CDN](https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js) to load modernizr. You can choose to use your own CDN as well. 44 | 45 | cdn: 'https://YourCDN.com/modernizr/...' 46 | 47 | **config**: Object 48 | 49 | Set the config option to create custom builds per client. **NOTE**: this create the same build every time someone new makes a request. This can potentially add more dynamics. If you don't need this, Use the *build* option below. 50 | 51 | config: { 52 | "minify": true, 53 | "options": [ 54 | "hasEvent", 55 | "html5shiv" 56 | ], 57 | "feature-detects": [ 58 | "canvas", 59 | "webanimations" 60 | ] 61 | } 62 | 63 | For a full list of **feature-detects** checkout [config-all.json](https://github.com/Modernizr/Modernizr/blob/master/lib/config-all.json) 64 | 65 | **build**: String 66 | 67 | If you already have a preset modernizr build, you can use it with the fs module like this: 68 | 69 | build: fs.readFileSync('./modernizr-custom.js', 'UTF-8') 70 | 71 | You can make a custom modernizr build [on their website.](https://modernizr.com/download?setclasses) This is the most recommended option because node won't have to create a custom build every time someone different connects to your webapp, and it keeps the modernizr build small. 72 | 73 | **storageName**: String 74 | 75 | Default: 'modernizr' 76 | 77 | This will change the name of the cookie or session variable that modernizr-server sets. 78 | 79 | **expires**: Number 80 | 81 | Default: 0 (Session) 82 | 83 | This will change the expiration time of the modernizr cookie in ms. You should probably set this something very high. 84 | 85 | # Example (cookie) 86 | // server.js 87 | const express = require('express') 88 | const cookieParser = require('cookie-parser') 89 | const modernizrServer = require('modernizr-server') 90 | 91 | const app = express() 92 | 93 | app.use(cookieParser()) 94 | app.use(modernizrServer({ 95 | config: { 96 | "minify": true, 97 | "options": [ 98 | "hasEvent", 99 | "html5shiv" 100 | ], 101 | "feature-detects": [ 102 | "canvas", 103 | "webanimations" 104 | ] 105 | }, 106 | })) 107 | 108 | app.get('/', function(req, res) { 109 | let Modernizr = req.cookies.modernizr 110 | if (!Modernizr.canvas) { 111 | console.log('The client does not support canvas!') 112 | res.send('You should get a better web browser. :(') 113 | } else { 114 | console.log('The client supports canvas!') 115 | res.send('Yay, you support canvas! :)') 116 | } 117 | }) 118 | 119 | app.listen(8000, function(err) { 120 | if (err) throw err 121 | console.log('listening at http://localhost:8000/') 122 | }) 123 | 124 | 125 | # Example (session) 126 | // server.js 127 | const express = require('express') 128 | const session = require('express-session') 129 | const modernizrServer = require('modernizr-server') 130 | 131 | const app = express() 132 | 133 | app.use(session({ 134 | secret: 'MY_SECRET', 135 | resave: false, 136 | saveUninitialized: false 137 | })) 138 | 139 | app.use(modernizrServer({ 140 | storageMethod: 'session', 141 | config: { 142 | "minify": true, 143 | "options": [ 144 | "hasEvent", 145 | "html5shiv" 146 | ], 147 | "feature-detects": [ 148 | "canvas", 149 | "webanimations" 150 | ] 151 | }, 152 | })) 153 | 154 | app.get('/', function(req, res) { 155 | let Modernizr = req.session.modernizr // NOT 'req.cookies' 156 | if (!Modernizr.canvas) { 157 | console.log('The client does not support canvas!') 158 | res.send('You should get a better web browser. :(') 159 | } else { 160 | console.log('The client supports canvas!') 161 | res.send('Yay, you support canvas! :)') 162 | } 163 | }) 164 | 165 | app.listen(8000, function(err) { 166 | if (err) throw err 167 | console.log('listening at http://localhost:8000/') 168 | }) 169 | 170 | # Using the same modernizr on the client 171 | This is not currently supported with sessions. Add this to the beginning of your javascript to gain access to modernizr. 172 | 173 | var Modernizr = document.cookies.modernizr 174 | 175 | # Why? 176 | Sometimes, it is necessary to have client data on the server. If your website heavily relies on your client having a certain feature, it might be better to handle this on the server instead of loading the whole app to a client who can't run it. 177 | 178 | # How it works 179 | modernizr-server will load a modernizr build onto the client. It will detect the functionality that you desire, then set a JSON cookie of that data. Once this is complete, the page will refresh and the server will then have access to this cookie (i.e. req.cookies.modernizr). As long as the client has the modernizr cookie, the server will never load modernizr to the client ever again. 180 | 181 | # Credit 182 | This was inspired by [rack-modernizr](https://github.com/marshally/rack-modernizr) 183 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const modernizr = require('modernizr') 2 | const bodyParser = require('body-parser') 3 | 4 | function modernizrExpress (options={}) { 5 | return (req, res, next) => { 6 | if (options.storageMethod == 'cookie') { 7 | handleCookie(req, res, next, options) 8 | } else if (options.storageMethod === 'session') { 9 | bodyParser.urlencoded({ extended: false })(req, res, handleSession(req, res, next, options)) 10 | } else { 11 | handleCookie(req, res, next, options) // default 12 | } 13 | } 14 | } 15 | 16 | function handleCookie(req, res, next, options) { 17 | let storageName = options.storageName || 'modernizr' // default to 'modernizr' 18 | if (req.cookies === undefined) { 19 | throw (Error(noCookieMessage(req.cookies[storageName]))) 20 | } 21 | if (req.cookies[storageName]) { 22 | req.cookies[storageName] = JSON.parse(req.cookies[storageName]) 23 | // cookie already exists, continue 24 | next() 25 | } else { 26 | let expires = options.expires || 0 // default to expires=Session 27 | if (options.build) { 28 | let build = options.build 29 | let scriptToSend = modernizrJsBuild(build, storageName, setModernizrCookieJs, expires) 30 | res.send(scriptToSend) 31 | } else if (options.config) { 32 | // use the config and make a build... 33 | modernizr.build(options.config, build => { 34 | let scriptToSend = modernizrJsBuild(build, storageName, setModernizrCookieJs, expires) 35 | res.send(scriptToSend) 36 | }) 37 | } else { 38 | // use a pre-configured CDN 39 | let cdn = options.cdn || 'https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js' 40 | let scriptToSend = modernizrJsCDN(cdn, storageName, setModernizrCookieJs, expires) 41 | res.send(scriptToSend) 42 | } 43 | } 44 | } 45 | 46 | function handleSession(req, res, next, options) { 47 | let storageName = options.storageName || 'modernizr' // default to 'modernizr' 48 | req.session.expires = options.expires || req.session.expires 49 | return () => { 50 | if (req.session === undefined) { 51 | throw (Error('Something went wrong')) 52 | // throw (Error(noCookieMessage(req.session[storageName]))) 53 | } 54 | if (req.session[storageName]) { 55 | next() 56 | } else if (req.query[storageName] ) { // the request from the javascript 57 | req.session[storageName] = JSON.parse(req.query[storageName]) 58 | next() 59 | } else { // the original request 60 | if (options.build) { 61 | let build = options.build 62 | let scriptToSend = modernizrJsBuild(build, storageName, setModernizrSessionJs) 63 | res.send(scriptToSend) 64 | } else if (options.config) { 65 | // use the config and make a build... 66 | modernizr.build(options.config, build => { 67 | let scriptToSend = modernizrJsBuild(build, storageName, setModernizrSessionJs) 68 | res.send(scriptToSend) 69 | }) 70 | } else { 71 | // use a pre-configured CDN 72 | let cdn = options.cdn || 'https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js' 73 | let scriptToSend = modernizrJsCDN(cdn, storageName, setModernizrSessionJs) 74 | res.send(scriptToSend) 75 | } 76 | } 77 | } 78 | } 79 | 80 | function noCookieMessage(storageName) { 81 | return `Cannot read property '${storageName}' of undefined 82 | 83 | Make sure you have cookie-parser installed, and is used before modernizr-server. 84 | 85 | Try: 86 | 87 | npm install cookie-parser 88 | 89 | and 90 | 91 | const cookieParser = require('cookie-parser') 92 | const modernizrServer = require('modernizr-server') 93 | 94 | app.use(cookieParser()) 95 | app.use(modernizrServer()) 96 | 97 | ... 98 | 99 | see https://www.github.com/rlindskog/modernizr-server/ for more information 100 | ` 101 | } 102 | 103 | function modernizrJsBuild (modernizrBuild, storageName, setModernizr, expires=0) { 104 | return ` 105 | 106 | 107 | ` 108 | } 109 | 110 | function modernizrJsCDN (modernizrCDN, storageName, setModernizr, expires=0) { 111 | return ` 112 | 113 | 114 | ` 115 | } 116 | 117 | function setModernizrCookieJs (storageName, expires) { 118 | let expiresStr = expires === 0 ? `let expiresDate = 0` : ` 119 | let now = new Date() 120 | now.setTime(now.getTime()+${expires}) 121 | let expiresDate = now.toGMTString() 122 | ` 123 | return ` 124 | ${expiresStr} 125 | try { 126 | document.cookie = '${storageName}=' + JSON.stringify(Modernizr) + '; expires=' + expiresDate + ';'; 127 | document.location.reload(); 128 | } catch(e) {} 129 | ` 130 | } 131 | 132 | function setModernizrSessionJs (storageName, expires) { 133 | return ` 134 | try { 135 | function sendModernizr() { 136 | let xhttp = new XMLHttpRequest(); 137 | let payload = JSON.stringify(Modernizr) 138 | xhttp.open("GET", document.location.pathname + '?${storageName}=' + payload, true); 139 | xhttp.send(); 140 | document.location.reload(); 141 | } 142 | sendModernizr(); 143 | } catch(e) {}; 144 | ` 145 | } 146 | 147 | module.exports = modernizrExpress 148 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modernizr-server", 3 | "version": "1.0.8", 4 | "description": "Express middleware that exposes client modernizr data to the server.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://www.github.com/rlindskog/modernizr-server" 12 | }, 13 | "author": "Ryan Lindskog", 14 | "license": "MIT", 15 | "dependencies": { 16 | "body-parser": "^1.15.2", 17 | "modernizr": "^3.3.1" 18 | }, 19 | "devDependencies": { 20 | "cookie-parser": "^1.4.3", 21 | "express": "^4.14.0", 22 | "eslint-config-standard": "^6.2.1", 23 | "eslint-plugin-promise": "^3.4.0", 24 | "eslint-plugin-standard": "^2.0.1" 25 | } 26 | } 27 | --------------------------------------------------------------------------------