// for testing without killing current version // PORT=7789 node . // if validating tests can replace via e.g // killall onhygi ; nohup node . & // to add on tridactyl : // autocmd DocStart .* js fetch('http://localhost:7788/check?url='+window.location.href).then(r => r.json()).then( r => { if(!r.passed) { if (r.redirect) { window.location.href = r.redirect } else { window.location.href = 'https://ggsc.berkeley.edu/'} } } ) const http = require("http"); const fs = require("fs"); const path = require("path"); const express = require("express"); // could be good to replace with code, no dep const port = process.env.PORT || 7788; const configFilePath = path.resolve(__dirname, "config.json") const usageFilePath = path.resolve(__dirname, "usage.json") process.title = 'onhygi' // didn't work on its own with ^C nor killall process.on('beforeExit', _ => { console.log('trying to save before exit') }); // process.on('exit', _ => { console.log('trying to save on exit') }); // catching signals and do something before exit ['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGTERM' ].forEach(function (sig) { process.on(sig, function () { terminator(sig); console.log('signal: ' + sig); }); }); function terminator(sig) { if (typeof sig === "string") { console.log('Received %s - terminating server app ...', sig); console.log('trying to save on exit') if (quota.length) fs.writeFileSync(usageFilePath, JSON.stringify( usage )) // to avoiding busting usage quota process.exit(1); } console.log('Node server stopped.'); } // from https://stackoverflow.com/a/40574758/1442164 var usage = {} if (fs.existsSync( usageFilePath ) ){ console.log('found usage file') usage = JSON.parse( fs.readFileSync(usageFilePath).toString() ) } console.log('past usage', usage) // does not apply in a P2P fashion, must rely on local configuration here config.json let quota = [ // unlimited {id:"ownhome", pattern:/.*fabien\.benetou\.fr.*/, perHour:10, perDay:200}, {id:"twitter",pattern:/.*twitter\.com.*/,perHour:1,perDay:12}, {id:"proton",pattern:/.*proton\.me.*/,perHour:6,perDay:6*12}, {id:"yt",pattern:/https:\/\/www\.youtube\.com.*/,perHour:2,perDay:12}, // allow for redirection {id:"yts",pattern:/https:\/\/youtube\.com.*/,perHour:2,perDay:12}, // allow for redirection, consider merged IDs {id:"linkedin",pattern:/.*linkedin\.com.*/,perHour:1,perDay:12}, {id:"reddit",pattern:/.*reddit\.com.*/,perHour:3,perDay:12,redirect:'https://lemmy.world'}, // redirection then enough to read private messages // reconsider the tridactyl check for permanently open pages, e.g TabEnter rather than DocStart {id:"element",pattern:/.*element\.io.*/,perHour:1,perDay:12}, {id:"discord",pattern:/.*discord\.com.*/,perHour:1,perDay:12}, ] // could also check on time, e.g not before 7am nor after 10pm as done via tridactyl now // bypass for e.g presentations //quota = [] if (fs.existsSync( configFilePath ) ){ const configurationFromFile = JSON.parse( fs.readFileSync( configFilePath ).toString() ) console.log('found quota file') quota = configurationFromFile } console.log('quota', quota) quota.map( rule => { if (typeof usage[rule.id] == 'undefined') usage[rule.id] = [] } ) // Setup and configure Express http server. const app = express(); // CORS app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); }); app.get('/', (req, res) => { res.json( quota ) }) app.get('/currentusage', (req, res) => { res.json( quota.map( rule => { let leftOnRule = { id: rule.id } let now = Date.now()/1000 leftOnRule['perHour'] = rule.perHour - usage[rule.id].filter( t => t>now-60*60).length leftOnRule['perDay'] = rule.perDay - usage[rule.id].filter( t => t>now-60*60*24).length return leftOnRule }) ) }) app.get('/check', (req, res) => { console.log('starting check') if (!req.query.url) { res.sendStatus(400); console.log('no url found') return; } let matched = false quota.map( rule => { if ( req.query.url.match( rule.pattern ) ){ matched = true console.log('match on', rule.id) let now = Date.now()/1000 usage[rule.id].push( now ) if ( usage[rule.id].filter( t => t>now-60*60).length > rule.perHour ){ if (rule.redirect) res.json( { passed: false, msg: 'over quota per day', redirect:rule.redirect } ) else res.json( { passed: false, msg: 'over quota per day' } ) return } if ( usage[rule.id].filter( t => t>now-60*60*24).length > rule.perDay ){ if (rule.redirect) res.json( { passed: false, msg: 'over quota per day', redirect:rule.redirect } ) else res.json( { passed: false, msg: 'over quota per day' } ) return } res.json( { passed: true, msg: 'within quota' } ) return // returns from map, not get } }) if (!matched) { res.json( { passed: true, msg: 'no quota' } ) console.log('no match') } // otherwise assume response already sent }) /* const privateKey = fs.readFileSync("naf-key.pem", "utf8"); const certificate = fs.readFileSync("naf.pem", "utf8"); const credentials = { key: privateKey, cert: certificate }; const webServer = https.createServer(credentials, app); */ // Start Express http server const webServer = http.createServer(app); // Listen on port webServer.listen(port, () => { console.log("listening on http://localhost:" + port); });