UNPKG

libre

Version:
174 lines (142 loc) 5.54 kB
import React from 'react'; import express from 'express'; import compression from 'compression'; import bodyParser from 'body-parser'; import path from 'path'; import HBS from 'express-handlebars'; import proxy from 'proxy-middleware'; import url from 'url'; import {renderToStaticMarkup, renderToString} from 'react-dom/server'; import Sheet from './Sheet'; import Libre from '../Libre'; import SEO from './SEO'; export default class Server { constructor(app, routes, typeMap, plugins, libreConfig, server) { this.hbs = 'index'; this.router = server || express(); this.routes = routes || {}; this.libreConfig = libreConfig; this.app = app; //seo-friendly version of app component Libre.setup({typeMap, plugins}); } serve() { const router = this.router; //1. setup app router.use(bodyParser.json()); router.use(bodyParser.urlencoded({ extended: true })); var port = process.env.PORT || 8081; //redirect http to https on production router.get('*', (req,res,next) => { if (req.headers['x-forwarded-proto'] != 'https' && process.env.NODE_ENV === 'production') { res.redirect('https://'+req.hostname+req.url); } else { next() /* Continue to other routes if we're not redirecting */ } }); //use gzip compression router.use(compression()); //config handlebars router.engine('hbs', HBS({ extname: '.hbs', defaultLayout: 'index', layoutsDir: './' })); router.set('views', path.resolve('./')); router.set('view engine', 'hbs'); //handle static assets at /assets //TODO: make these configurable! router.use('/assets', express.static(path.resolve('./assets'))); router.use('/client.js', express.static(path.resolve('./client.js'))); router.use('/style.css', express.static(path.resolve('./style.css'))); //listen for all other routes to power app router.get('*', (req, res) => { const context = {}; let args = {}; //this enables individual app pages to emit their own meta tags //if implemented, the call to renderToStaticMarkup() will cause this to populate args //so it's aptly named, cause this is pretty fucking og. const og = (meta) => args = Object.assign({}, args, meta); const app = <SEO location={req.url} routes={this.routes} app={this.app} libre={Libre} og={og} /> const html = renderToStaticMarkup(app); const urlBase = `${req.protocol}://${req.hostname}`; args.app = html; args.url = `${urlBase}${req.url}`; //special case for images passed through og() with server-relative paths) //we need to tack on the url base here to make sure it's exposed to OG as full url if (args.image && !/https?\:\/\//.test(args.image)) { if (args.image.startsWith('/')) args.image = `${urlBase}${args.image}`; else args.image = `${urlBase}${req.url}/${args.image}` } res.render(this.hbs, args); }); //listen and we're done! router.listen(port, () => { console.log('[LIBRE] Server listening on port', port); }); } //access the singleton instance of Libre from externally to make sure it's the same instance get libre() { return Libre; } load(creds, key, tabs, store, success) { var loaded = 0; const sheet = new Sheet(creds); tabs.map(tab => { sheet.loadSheet(key, tab, (content) => { store[tab] = content; loaded += 1; if (loaded == tabs.length && typeof(success) == 'function') success(store); }); }); } run(existingServer) { const store = {}; const {tabs, sheetKey, sheetCreds, writeKey, writeTab} = this.libreConfig; const routes = this.routes; const router = this.router || existingServer; //TODO: figure out where a setup() function should go //these need to be here in order to enable post handling to work properly router.use(bodyParser.json()); router.use(bodyParser.urlencoded({ extended: true })); //load all content in according to config this.load(sheetCreds, sheetKey, tabs, store, (store) => { //once content is loaded from spreadsheet, also load it into memory //this is used for SEO rendering, so that all content is available at request Libre.init(store); //only launch server if existing server was not passed in if (!existingServer) this.serve(); }); //listen for content requests from client router.get('/libre/:path?', (req, res) => { const {path} = req.params; //if path is specified send just that object (one tab) //(this may be undefined --> send null) if (path) res.send(store[path] || null); //otherwise send the whole store else res.send(store); }); //enable client to invoke content refresh on server //TODO: figure out how to secure this router.post('/libre/refresh', (req, res) => { this.load(sheetCreds, sheetKey, tabs, store, (store) => { Libre.init(store); res.send(store); }); }); //if writeKey and writeTab are enabled, also activate listener for writes if (writeKey && writeTab) { router.post('/libre/push', (req, res) => { const {tab, row} = req.body; if (tab && row) { const sheet = new Sheet(sheetCreds); sheet.addRow(writeKey, tab, row, () => { res.send({success: true}); }); } else { res.send({success: false}); } }); } } }