<commit_before>/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import 'babel-polyfill';
import path from 'path';
import express from 'express';
import session from 'express-session'
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
//import expressJwt from 'express-jwt';
import expressGraphQL from 'express-graphql';
import jwt from 'jsonwebtoken';
import React from 'react';
import ReactDOM from 'react-dom/server';
import Html from './components/Html';
import { ErrorPage } from './routes/error/ErrorPage';
import errorPageStyle from './routes/error/ErrorPage.css';
import UniversalRouter from 'universal-router';
import PrettyError from 'pretty-error';
import passport from 'passport';
import DiscordStrategy from 'passport-discord';
import logger from 'morgan';
import models from './data/models';
import schema from './data/schema';
import routes from './routes';
import assets from './assets'; // eslint-disable-line import/no-unresolved
import { port, auth } from './config';
//import db from './db';

var scopes = ['identify', 'email', /* 'connections', (it is currently broken) */ 'guilds', 'guilds.join'];

const app = express();

passport.serializeUser(function(user, done) {
  done(null, user);
});
passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

//
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
// user agent is not known.
// -----------------------------------------------------------------------------
global.navigator = global.navigator || {};
global.navigator.userAgent = global.navigator.userAgent || 'all';

//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//
// Authentication
// -----------------------------------------------------------------------------
passport.use(new DiscordStrategy(
  {
    clientID: auth.discord.id,
    clientSecret: auth.discord.secret,
    callbackURL: '/login/discord/callback',
    scope: scopes
  },
  function(accessToken, refreshToken, profile, cb) {
    process.nextTick(function() {
      return cb(null, profile);
    });
  }
));

app.use(session({
  secret: 'keyboard cat-acomb',
  resave: false,
  saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.get('/login/discord', passport.authenticate('discord', { scope: scopes }, function(req, res) {}));
app.get('/login/discord/callback',
  passport.authenticate('discord', { failureRedirect: '/login' }), function(req, res) { res.redirect('/server') } // auth success
);
app.get('/logout', function(req, res) {
  req.logout();
  res.redirect('/');
});
app.get('/info', checkAuth, function(req, res) {
  //console.log(req.user)
  res.json(req.user);
  //res.send("Welcome " + req.user.username);
});

//
// Register API middleware
// -----------------------------------------------------------------------------
app.use('/graphql', expressGraphQL(req => ({
  schema,
  graphiql: true,
  rootValue: { request: req },
  pretty: process.env.NODE_ENV !== 'production',
})));

//
// Register server-side rendering middleware
// -----------------------------------------------------------------------------
app.get('*', async (req, res, next) => {
  try {
    let css = [];
    let statusCode = 200;
    const data = { title: '', description: '', style: '', script: assets.main.js, children: '' };

    await UniversalRouter.resolve(routes, {
      req: req,
      user: req.user,
      path: req.path,
      query: req.query,
      context: {
        insertCss: (...styles) => {
          styles.forEach(style => css.push(style._getCss())); // eslint-disable-line no-underscore-dangle, max-len
        },
        setTitle: value => (data.title = value),
        setMeta: (key, value) => (data[key] = value),
      },
      render(component, status = 200) {
        css = [];
        statusCode = status;
        data.children = ReactDOM.renderToString(component);
        data.style = css.join('');
        return true;
      },
    });

    const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);

    res.status(statusCode);
    res.send(`<!doctype html>${html}`);
  } catch (err) {
    next(err);
  }
});

//
// Error handling
// -----------------------------------------------------------------------------
const pe = new PrettyError();
pe.skipNodeFiles();
pe.skipPackage('express');

app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
  console.log(pe.render(err)); // eslint-disable-line no-console
  const statusCode = err.status || 500;
  const html = ReactDOM.renderToStaticMarkup(
    <Html
      title="Internal Server Error"
      description={err.message}
      style={errorPageStyle._getCss()} // eslint-disable-line no-underscore-dangle
    >
      {ReactDOM.renderToString(<ErrorPage error={err} />)}
    </Html>
  );
  res.status(statusCode);
  res.send(`<!doctype html>${html}`);
});

//
// Launch the server
// -----------------------------------------------------------------------------
/* eslint-disable no-console */
models.sync().catch(err => console.error(err.stack)).then(() => {
  app.listen(port, () => {
    console.log(`The server is running at http://localhost:${port}/`);
  });
});
/* eslint-enable no-console */

function checkAuth(req, res, next) {
  if (req.isAuthenticated()) return next();
  res.redirect('/');
}
<commit_msg>request a more reasonable scope<commit_after>/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import 'babel-polyfill';
import path from 'path';
import express from 'express';
import session from 'express-session'
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
//import expressJwt from 'express-jwt';
import expressGraphQL from 'express-graphql';
import jwt from 'jsonwebtoken';
import React from 'react';
import ReactDOM from 'react-dom/server';
import Html from './components/Html';
import { ErrorPage } from './routes/error/ErrorPage';
import errorPageStyle from './routes/error/ErrorPage.css';
import UniversalRouter from 'universal-router';
import PrettyError from 'pretty-error';
import passport from 'passport';
import DiscordStrategy from 'passport-discord';
import logger from 'morgan';
import models from './data/models';
import schema from './data/schema';
import routes from './routes';
import assets from './assets'; // eslint-disable-line import/no-unresolved
import { port, auth } from './config';
//import db from './db';

var scopes = ['identify', /* 'connections', (it is currently broken) */ 'guilds'];

const app = express();

passport.serializeUser(function(user, done) {
  done(null, user);
});
passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

//
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
// user agent is not known.
// -----------------------------------------------------------------------------
global.navigator = global.navigator || {};
global.navigator.userAgent = global.navigator.userAgent || 'all';

//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//
// Authentication
// -----------------------------------------------------------------------------
passport.use(new DiscordStrategy(
  {
    clientID: auth.discord.id,
    clientSecret: auth.discord.secret,
    callbackURL: '/login/discord/callback',
    scope: scopes
  },
  function(accessToken, refreshToken, profile, cb) {
    process.nextTick(function() {
      return cb(null, profile);
    });
  }
));

app.use(session({
  secret: 'keyboard cat-acomb',
  resave: false,
  saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.get('/login/discord', passport.authenticate('discord', { scope: scopes }, function(req, res) {}));
app.get('/login/discord/callback',
  passport.authenticate('discord', { failureRedirect: '/login' }), function(req, res) { res.redirect('/server') } // auth success
);
app.get('/logout', function(req, res) {
  req.logout();
  res.redirect('/');
});
app.get('/info', checkAuth, function(req, res) {
  //console.log(req.user)
  res.json(req.user);
  //res.send("Welcome " + req.user.username);
});

//
// Register API middleware
// -----------------------------------------------------------------------------
app.use('/graphql', expressGraphQL(req => ({
  schema,
  graphiql: true,
  rootValue: { request: req },
  pretty: process.env.NODE_ENV !== 'production',
})));

//
// Register server-side rendering middleware
// -----------------------------------------------------------------------------
app.get('*', async (req, res, next) => {
  try {
    let css = [];
    let statusCode = 200;
    const data = { title: '', description: '', style: '', script: assets.main.js, children: '' };

    await UniversalRouter.resolve(routes, {
      req: req,
      user: req.user,
      path: req.path,
      query: req.query,
      context: {
        insertCss: (...styles) => {
          styles.forEach(style => css.push(style._getCss())); // eslint-disable-line no-underscore-dangle, max-len
        },
        setTitle: value => (data.title = value),
        setMeta: (key, value) => (data[key] = value),
      },
      render(component, status = 200) {
        css = [];
        statusCode = status;
        data.children = ReactDOM.renderToString(component);
        data.style = css.join('');
        return true;
      },
    });

    const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);

    res.status(statusCode);
    res.send(`<!doctype html>${html}`);
  } catch (err) {
    next(err);
  }
});

//
// Error handling
// -----------------------------------------------------------------------------
const pe = new PrettyError();
pe.skipNodeFiles();
pe.skipPackage('express');

app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
  console.log(pe.render(err)); // eslint-disable-line no-console
  const statusCode = err.status || 500;
  const html = ReactDOM.renderToStaticMarkup(
    <Html
      title="Internal Server Error"
      description={err.message}
      style={errorPageStyle._getCss()} // eslint-disable-line no-underscore-dangle
    >
      {ReactDOM.renderToString(<ErrorPage error={err} />)}
    </Html>
  );
  res.status(statusCode);
  res.send(`<!doctype html>${html}`);
});

//
// Launch the server
// -----------------------------------------------------------------------------
/* eslint-disable no-console */
models.sync().catch(err => console.error(err.stack)).then(() => {
  app.listen(port, () => {
    console.log(`The server is running at http://localhost:${port}/`);
  });
});
/* eslint-enable no-console */

function checkAuth(req, res, next) {
  if (req.isAuthenticated()) return next();
  res.redirect('/');
}
<|endoftext|>


<commit_before>const express = require('express')
const app = express()
const sendSMS = require('./lib/Twilio.js')
require('dotenv').config()
const bodyParser = require('body-parser')
const methodOverride = require('method-override')

const {
  createTimer,
  startTimer,
  stopTimer,
  resetTimer,
  decrementTimer
} = require('./timers.js')

const {
  createAlert,
  editAlert,
  triggerAlert
} = require('./alerts.js')

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json())

app.use(methodOverride());

let timersArray = []
let alertsArray = []

const updateTimer = timerId => {
  const timer = timersArray.filter(timer => timer.id === timerId)[0]
  const updatedTimer = decrementTimer(timer)
  timersArray = timersArray.map(t => {
    return (t.id === updatedTimer.id)
    ? updatedTimer
    : t
  })
}

// ## CORS middleware
//
// see: http://stackoverflow.com/questions/7067966/how-to-allow-cors-in-express-nodejs
const allowCrossDomain = (req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

    // intercept OPTIONS method
    if ('OPTIONS' == req.method) {
      res.sendStatus(200);
    }
    else {
      next();
    }
};
app.use(allowCrossDomain);

app.post('/messages', (req, res) => {
  sendSMS(req.body.message)
  res.sendStatus(200)
})

app.get('/alert/:id', (req, res) => {
  const alert = alertsArray.filter(alert => alert.id === req.params.id)[0]
  res.send(alert)
})

app.post('/alert/:id/edit', (req, res) => {
  const alert = alertsArray.filter(alert => alert.id === req.params.id)[0]
  const editedAlert = editAlert(alert, req.body.newProps)
  alertsArray = alertsArray.map(a => {
    return (a.id === editedAlert.id)
    ? editedAlert
    : a
  })
  res.send(editedAlert)
})

app.post('/alert/:id/trigger', (req, res) => {
  const alert = alertsArray.filter(alert => alert.id === req.params.id)[0]
  const triggeredAlert = triggerAlert(alert)
  alertsArray = alertsArray.map(a => {
    return (a.id === triggeredAlert.id)
    ? editedAlert
    : a
  })
  res.send(triggeredAlert)
})

app.get('/alerts', (req, res) => {
  res.send(alertsArray)
})

app.post('/alerts/new', (req, res) => {
  const {description, triggerTime, timerId} = req.body
  const alert = createAlert(description, triggerTime, timerId)
  alertsArray = [...alertsArray, alert]
  res.send(alert)
})

app.get('/timer/:id', (req, res) => {
 const timer = timersArray.filter(timer => timer.id === req.params.id)[0]
 res.send(timer)
})

app.post('/timer/:id/start', (req, res) => {
  const timer = timersArray.filter(timer => timer.id === req.params.id)[0]
  const startedTimer = startTimer(timer)
  const interval = setInterval(updateTimer, 1000, startedTimer.id)
  startedTimer.interval = interval
  timersArray = timersArray.map(t => {
    return (t.id === startedTimer.id)
    ? startedTimer
    : t
  })
  res.sendStatus(200)
})

app.post('/timer/:id/stop', (req, res) => {
  const timer = timersArray.filter(timer => timer.id === req.params.id)[0]
  const stoppedTimer = stopTimer(timer)
  clearInterval(timer.interval)
  stoppedTimer.interval = null
  timersArray = timersArray.map(t => {
    return (t.id === stoppedTimer.id)
    ? stoppedTimer
    : t
  })
  res.sendStatus(200)
})

app.post('/timer/:id/reset', (req, res) => {
  const timer = timersArray.filter(timer => timer.id === req.params.id)[0]
  const resetedTimer = resetTimer(timer)
  timersArray = timersArray.map(t => {
    return (t.id === resetedTimer.id)
    ? resetedTimer
    : t
  })
  res.sendStatus(200)
})

app.get('/timers', (req, res) => {
  res.send(timersArray)
})

app.post('/timers/new', (req, res) => {
  const timer = createTimer(req.body.initialMinutes)
  timersArray = [...timersArray, timer]
  res.send(timer)
})

app.listen(process.env.PORT, () => {
  console.log('Example app listening on port ' + process.env.PORT)
})<commit_msg>Add checkAlerts, remove messages endpoint<commit_after>const express = require('express')
const app = express()
const sendSMS = require('./lib/Twilio.js')
require('dotenv').config()
const bodyParser = require('body-parser')
const methodOverride = require('method-override')

const {
  createTimer,
  startTimer,
  stopTimer,
  resetTimer,
  decrementTimer
} = require('./timers.js')

const {
  createAlert,
  editAlert,
  triggerAlert
} = require('./alerts.js')

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json())

app.use(methodOverride());

let timersArray = []
let alertsArray = []

const checkAlerts = (timerId, time) => {
  alertsArray = alertsArray.map(alert => {
    if(alert.timerId === timerId && !alert.triggered && alert.triggerTime === time){
      sendSMS(alert.description)
      return Object.assign({}, alert, {triggered: true})
    }else{
      return alert
    }
  })
}

const updateTimer = timerId => {
  const timer = timersArray.filter(timer => timer.id === timerId)[0]
  const updatedTimer = decrementTimer(timer)
  checkAlerts(updatedTimer.id, updatedTimer.currentTime)
  timersArray = timersArray.map(t => {
    return (t.id === updatedTimer.id)
    ? updatedTimer
    : t
  })
}

// ## CORS middleware
//
// see: http://stackoverflow.com/questions/7067966/how-to-allow-cors-in-express-nodejs
const allowCrossDomain = (req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

    // intercept OPTIONS method
    if ('OPTIONS' == req.method) {
      res.sendStatus(200);
    }
    else {
      next();
    }
};
app.use(allowCrossDomain);

app.get('/alert/:id', (req, res) => {
  const alert = alertsArray.filter(alert => alert.id === req.params.id)[0]
  res.send(alert)
})

app.post('/alert/:id/edit', (req, res) => {
  const alert = alertsArray.filter(alert => alert.id === req.params.id)[0]
  const editedAlert = editAlert(alert, req.body.newProps)
  alertsArray = alertsArray.map(a => {
    return (a.id === editedAlert.id)
    ? editedAlert
    : a
  })
  res.send(editedAlert)
})

app.post('/alert/:id/trigger', (req, res) => {
  const alert = alertsArray.filter(alert => alert.id === req.params.id)[0]
  const triggeredAlert = triggerAlert(alert)
  alertsArray = alertsArray.map(a => {
    return (a.id === triggeredAlert.id)
    ? editedAlert
    : a
  })
  res.send(triggeredAlert)
})

app.get('/alerts', (req, res) => {
  res.send(alertsArray)
})

app.post('/alerts/new', (req, res) => {
  const {description, triggerTime, timerId} = req.body
  const alert = createAlert(description, triggerTime, timerId)
  alertsArray = [...alertsArray, alert]
  res.send(alert)
})

app.get('/timer/:id', (req, res) => {
 const timer = timersArray.filter(timer => timer.id === req.params.id)[0]
 res.send(timer)
})

app.post('/timer/:id/start', (req, res) => {
  const timer = timersArray.filter(timer => timer.id === req.params.id)[0]
  const startedTimer = startTimer(timer)
  const interval = setInterval(updateTimer, 1000, startedTimer.id)
  startedTimer.interval = interval
  timersArray = timersArray.map(t => {
    return (t.id === startedTimer.id)
    ? startedTimer
    : t
  })
  res.sendStatus(200)
})

app.post('/timer/:id/stop', (req, res) => {
  const timer = timersArray.filter(timer => timer.id === req.params.id)[0]
  const stoppedTimer = stopTimer(timer)
  clearInterval(timer.interval)
  stoppedTimer.interval = null
  timersArray = timersArray.map(t => {
    return (t.id === stoppedTimer.id)
    ? stoppedTimer
    : t
  })
  res.sendStatus(200)
})

app.post('/timer/:id/reset', (req, res) => {
  const timer = timersArray.filter(timer => timer.id === req.params.id)[0]
  const resetedTimer = resetTimer(timer)
  timersArray = timersArray.map(t => {
    return (t.id === resetedTimer.id)
    ? resetedTimer
    : t
  })
  res.sendStatus(200)
})

app.get('/timers', (req, res) => {
  res.send(timersArray)
})

app.post('/timers/new', (req, res) => {
  const timer = createTimer(req.body.initialMinutes)
  timersArray = [...timersArray, timer]
  res.send(timer)
})

app.listen(process.env.PORT, () => {
  console.log('Example app listening on port ' + process.env.PORT)
})<|endoftext|>


<commit_before>/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import path from 'path';
import express from 'express';
import cookieParser from 'cookie-parser';
import requestLanguage from 'express-request-language';
import bodyParser from 'body-parser';
import expressGraphQL from 'express-graphql';
import React from 'react';
import ReactDOM from 'react-dom/server';
import UniversalRouter from 'universal-router';
import PrettyError from 'pretty-error';
import session from 'express-session';
import knexSession from 'connect-session-knex';
import memorySession from 'session-memory-store';
import { IntlProvider } from 'react-intl';
import knex from './data/knex';
import './serverIntlPolyfill';
import App from './components/App';
import Html from './components/Html';
import { ErrorPageWithoutStyle } from './routes/error/ErrorPage';
import errorPageStyle from './routes/error/ErrorPage.css';
import schema from './data/schema';
import routes from './routes';
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
import configureStore from './store/configureStore';
import { setRuntimeVariable } from './actions/runtime';
import { setLocale } from './actions/intl';
import { port, locales } from './config';
import createLoaders from './data/dataLoader';
import passport from './core/passport';

const app = express();

//
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
// user agent is not known.
// -----------------------------------------------------------------------------
global.navigator = global.navigator || {};
global.navigator.userAgent = global.navigator.userAgent || 'all';

//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(express.static(path.join(__dirname, 'public')));

app.use(cookieParser());
app.use(
  requestLanguage({
    languages: locales,
    queryName: 'lang',
    cookie: {
      name: 'lang',
      options: {
        path: '/',
        maxAge: 3650 * 24 * 3600 * 1000, // 10 years in miliseconds
      },
      url: '/lang/{language}',
    },
  }),
);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//
// Sessions
// -----------------------------------------------------------------------------

const sessionConfig = {
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  cookie: { maxAge: 4 * 60 * 60 * 1000 },
  //cookie: { secure: true } // Use with SSL : https://github.com/expressjs/session
};
if (process.env.NODE_ENV === 'production') {
  const SessionStore = knexSession(session);
  const sessionDB = new SessionStore({
    knex,
  });
  sessionConfig.store = sessionDB;
} else {
  const SessionStore = memorySession(session);
  const sessionDB = new SessionStore();
  sessionConfig.store = sessionDB;
}

app.use(session(sessionConfig));

//
// Authentication
// -----------------------------------------------------------------------------
app.use(passport.initialize());
app.use(passport.session());

if (process.env.NODE_ENV !== 'production') {
  app.enable('trust proxy');
}

app.post(
  '/',
  passport.authenticate('local', {
    successRedirect: '/proposals/active',
  }),
);

app.get('/test', (req, res, next) => {
  knex('users')
    .where({ name: 'admin' })
    .join('roles', 'users.role_id', '=', 'roles.id')
    .select('type')
    .then(data => {
      res.status(200).json(data);
    })
    .catch(error => next(error));
});

//
// Register API middleware
// -----------------------------------------------------------------------------

app.use(
  '/graphql',
  expressGraphQL(req => ({
    schema,
    graphiql: process.env.NODE_ENV !== 'production',
    rootValue: { request: req },
    pretty: process.env.NODE_ENV !== 'production',
    context: {
      viewer: req.user,
      /* { id: 12, role: 'admin', name: 'admin', email: 'admin@example.com' },*/
      loaders: createLoaders(),
    },
  })),
);

//
// Register server-side rendering middleware
// -----------------------------------------------------------------------------
app.get('*', async (req, res, next) => {
  try {
    const store = configureStore(
      {
        user: req.user || null,
      },
      {
        cookie: req.headers.cookie,
      },
    );

    store.dispatch(
      setRuntimeVariable({
        name: 'initialNow',
        value: Date.now(),
      }),
    );

    store.dispatch(
      setRuntimeVariable({
        name: 'availableLocales',
        value: locales,
      }),
    );

    const locale = req.language;
    await store.dispatch(
      setLocale({
        locale,
      }),
    );

    const css = new Set();

    // Global (context) variables that can be easily accessed from any React component
    // https://facebook.github.io/react/docs/context.html
    const context = {
      // Enables critical path CSS rendering
      // https://github.com/kriasoft/isomorphic-style-loader
      insertCss: (...styles) => {
        // eslint-disable-next-line no-underscore-dangle
        styles.forEach(style => css.add(style._getCss()));
      },
      // Initialize a new Redux store
      // http://redux.js.org/docs/basics/UsageWithReact.html
      store,
    };

    const route = await UniversalRouter.resolve(routes, {
      ...context,
      path: req.path,
      query: req.query,
      locale,
    });
    if (route.redirect) {
      res.redirect(route.status || 302, route.redirect);
      return;
    }

    const data = { ...route };
    data.children = ReactDOM.renderToString(<App context={context}>{route.component}</App>);
    data.style = [...css].join('');
    data.scripts = [assets.vendor.js, assets.client.js];
    if (assets[route.chunk]) {
      data.scripts.push(assets[route.chunk].js);
    }
    data.state = context.store.getState();
    data.lang = locale;

    const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
    res.status(route.status || 200);
    res.send(`<!doctype html>${html}`);
  } catch (err) {
    next(err);
  }
});

//
// Error handling
// -----------------------------------------------------------------------------
const pe = new PrettyError();
pe.skipNodeFiles();
pe.skipPackage('express');

// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
  console.log(pe.render(err)); // eslint-disable-line no-console
  const locale = req.language;
  const html = ReactDOM.renderToStaticMarkup(
    <Html
      title="Internal Server Error"
      description={err.message}
      style={errorPageStyle._getCss()} // eslint-disable-line no-underscore-dangle
      lang={locale}
    >
      {ReactDOM.renderToString(
        <IntlProvider locale={locale}>
          <ErrorPageWithoutStyle error={err} />
        </IntlProvider>,
      )}
    </Html>,
  );
  res.status(err.status || 500);
  res.send(`<!doctype html>${html}`);
});

//
// Launch the server
// -----------------------------------------------------------------------------
/* eslint-disable no-console */

app.listen(port, () => {
  console.log(`The server is running at http://localhost:${port}/`);
});

/* eslint-enable no-console */
<commit_msg>also dev environment now uses knexSession store<commit_after>/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import path from 'path';
import express from 'express';
import cookieParser from 'cookie-parser';
import requestLanguage from 'express-request-language';
import bodyParser from 'body-parser';
import expressGraphQL from 'express-graphql';
import React from 'react';
import ReactDOM from 'react-dom/server';
import UniversalRouter from 'universal-router';
import PrettyError from 'pretty-error';
import session from 'express-session';
import knexSession from 'connect-session-knex';
import { IntlProvider } from 'react-intl';
import knex from './data/knex';
import './serverIntlPolyfill';
import App from './components/App';
import Html from './components/Html';
import { ErrorPageWithoutStyle } from './routes/error/ErrorPage';
import errorPageStyle from './routes/error/ErrorPage.css';
import schema from './data/schema';
import routes from './routes';
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
import configureStore from './store/configureStore';
import { setRuntimeVariable } from './actions/runtime';
import { setLocale } from './actions/intl';
import { port, locales } from './config';
import createLoaders from './data/dataLoader';
import passport from './core/passport';

const app = express();

//
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
// user agent is not known.
// -----------------------------------------------------------------------------
global.navigator = global.navigator || {};
global.navigator.userAgent = global.navigator.userAgent || 'all';

//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(express.static(path.join(__dirname, 'public')));

app.use(cookieParser());
app.use(
  requestLanguage({
    languages: locales,
    queryName: 'lang',
    cookie: {
      name: 'lang',
      options: {
        path: '/',
        maxAge: 3650 * 24 * 3600 * 1000, // 10 years in miliseconds
      },
      url: '/lang/{language}',
    },
  }),
);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

//
// Sessions
// -----------------------------------------------------------------------------

const sessionConfig = {
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  cookie: { maxAge: 4 * 60 * 60 * 1000 },
  //cookie: { secure: true } // Use with SSL : https://github.com/expressjs/session
};

const SessionStore = knexSession(session);
const sessionDB = new SessionStore({
  knex,
});
sessionConfig.store = sessionDB;

app.use(session(sessionConfig));

//
// Authentication
// -----------------------------------------------------------------------------
app.use(passport.initialize());
app.use(passport.session());

if (process.env.NODE_ENV !== 'production') {
  app.enable('trust proxy');
}

app.post(
  '/',
  passport.authenticate('local', {
    successRedirect: '/proposals/active',
  }),
);

app.get('/test', (req, res, next) => {
  knex('users')
    .where({ name: 'admin' })
    .join('roles', 'users.role_id', '=', 'roles.id')
    .select('type')
    .then(data => {
      res.status(200).json(data);
    })
    .catch(error => next(error));
});

//
// Register API middleware
// -----------------------------------------------------------------------------

app.use(
  '/graphql',
  expressGraphQL(req => ({
    schema,
    graphiql: process.env.NODE_ENV !== 'production',
    rootValue: { request: req },
    pretty: process.env.NODE_ENV !== 'production',
    context: {
      viewer: req.user,
      /* { id: 12, role: 'admin', name: 'admin', email: 'admin@example.com' },*/
      loaders: createLoaders(),
    },
  })),
);

//
// Register server-side rendering middleware
// -----------------------------------------------------------------------------
app.get('*', async (req, res, next) => {
  try {
    const store = configureStore(
      {
        user: req.user || null,
      },
      {
        cookie: req.headers.cookie,
      },
    );

    store.dispatch(
      setRuntimeVariable({
        name: 'initialNow',
        value: Date.now(),
      }),
    );

    store.dispatch(
      setRuntimeVariable({
        name: 'availableLocales',
        value: locales,
      }),
    );

    const locale = req.language;
    await store.dispatch(
      setLocale({
        locale,
      }),
    );

    const css = new Set();

    // Global (context) variables that can be easily accessed from any React component
    // https://facebook.github.io/react/docs/context.html
    const context = {
      // Enables critical path CSS rendering
      // https://github.com/kriasoft/isomorphic-style-loader
      insertCss: (...styles) => {
        // eslint-disable-next-line no-underscore-dangle
        styles.forEach(style => css.add(style._getCss()));
      },
      // Initialize a new Redux store
      // http://redux.js.org/docs/basics/UsageWithReact.html
      store,
    };

    const route = await UniversalRouter.resolve(routes, {
      ...context,
      path: req.path,
      query: req.query,
      locale,
    });
    if (route.redirect) {
      res.redirect(route.status || 302, route.redirect);
      return;
    }

    const data = { ...route };
    data.children = ReactDOM.renderToString(<App context={context}>{route.component}</App>);
    data.style = [...css].join('');
    data.scripts = [assets.vendor.js, assets.client.js];
    if (assets[route.chunk]) {
      data.scripts.push(assets[route.chunk].js);
    }
    data.state = context.store.getState();
    data.lang = locale;

    const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
    res.status(route.status || 200);
    res.send(`<!doctype html>${html}`);
  } catch (err) {
    next(err);
  }
});

//
// Error handling
// -----------------------------------------------------------------------------
const pe = new PrettyError();
pe.skipNodeFiles();
pe.skipPackage('express');

// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
  console.log(pe.render(err)); // eslint-disable-line no-console
  const locale = req.language;
  const html = ReactDOM.renderToStaticMarkup(
    <Html
      title="Internal Server Error"
      description={err.message}
      style={errorPageStyle._getCss()} // eslint-disable-line no-underscore-dangle
      lang={locale}
    >
      {ReactDOM.renderToString(
        <IntlProvider locale={locale}>
          <ErrorPageWithoutStyle error={err} />
        </IntlProvider>,
      )}
    </Html>,
  );
  res.status(err.status || 500);
  res.send(`<!doctype html>${html}`);
});

//
// Launch the server
// -----------------------------------------------------------------------------
/* eslint-disable no-console */

app.listen(port, () => {
  console.log(`The server is running at http://localhost:${port}/`);
});

/* eslint-enable no-console */
<|endoftext|>


<commit_before>import 'es6-promise/auto';
import { createStore, applyMiddleware, compose } from 'redux';
import { setOption } from './actions/options';
import { editActive, editInactive } from './actions/editable';
import { login, logout } from './actions/authentication';
import { get, set, remove, find } from './actions/data';
import { observeQuery } from './actions/queries';
import save from './actions/save';
import { AUTH_SERVER } from './constants/options';
import { DATA_PREFIX, QUERIES_PREFIX, PUBLIC_STATE_MAP } from './constants/state';
import * as types from './constants/actionTypes';
import { configurePolymer } from './utils/prepare';
import {
  storeToObserver,
  dispatchThunkAndExpect,
  pathToUid,
  selectPropByPath,
  itemUidToPath,
  queryResultsToPath,
  validatePath,
  toQueryParams,
  uidsToResponse
} from './utils/helpers';
import ping from './plugins/ping';
import persistToken from './plugins/persistToken';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

// Setup Polymer configuration
configurePolymer();

const Simpla = new class Simpla {
  constructor() {
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
    this._store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
  }

  init(project) {
    this._store.dispatch(setOption('project', project));

    // Initialize endpoints
    this._store.dispatch(setOption('authEndpoint', AUTH_SERVER));
    this._store.dispatch(setOption('dataEndpoint', `${AUTH_SERVER}/projects/${project}/content`));
  }

  // Authentication
  login(...args) {
    return dispatchThunkAndExpect(this._store, login(...args), types.LOGIN_SUCCESSFUL);
  }

  logout(...args) {
    return dispatchThunkAndExpect(this._store, logout(...args), types.LOGOUT_SUCCESSFUL);
  }

  // Data
  find(options = {}) {
    let parentPath = options.parent;
    options.parent = pathToUid(parentPath);
    return Promise.resolve()
      .then(() => validatePath(parentPath))
      .then(() => dispatchThunkAndExpect(
        this._store,
        find(options),
        types.FIND_DATA_SUCCESSFUL
      ))
      .then(queryResultsToPath);
  }

  get(path, ...args) {
    const uid = pathToUid(path);
    return Promise.resolve()
      .then(() => validatePath(path))
      .then(() => dispatchThunkAndExpect(
        this._store,
        get(uid, ...args),
        types.GET_DATA_SUCCESSFUL
      ))
      .then(itemUidToPath);
  }

  set(path, ...args) {
    const uid = pathToUid(path);
    return Promise.resolve()
      .then(() => validatePath(path))
      .then(() => dispatchThunkAndExpect(
        this._store,
        set(uid, ...args),
        types.SET_DATA_SUCCESSFUL
      ))
      .then(itemUidToPath);
  }

  remove(path, ...args) {
    const uid = pathToUid(path);
    return Promise.resolve()
      .then(() => validatePath(path))
      .then(() => dispatchThunkAndExpect(
        this._store,
        remove(uid, ...args),
        types.REMOVE_DATA_SUCCESSFUL
      ))
      .then(itemUidToPath);
  }

  observe(path, ...args) {
    let callback = args.pop(),
        uid = pathToUid(path),
        pathInState,
        wrappedCallback;

    if (!uid) {
      throw new Error('Observe must be given a valid path');
    }

    validatePath(path);

    pathInState = [ DATA_PREFIX, 'content', uid ];
    wrappedCallback = () => this.get(path).then(callback);

    return storeToObserver(this._store).observe(pathInState, wrappedCallback);
  }

  observeQuery(query, callback) {
    let queryString,
        pathInStore,
        wrappedCallback;

    // Clone so as to not affect given param
    query = Object.assign({}, query);

    query.parent = pathToUid(query.parent);
    queryString = toQueryParams(query);
    pathInStore = [ QUERIES_PREFIX, queryString, 'matches' ];

    this._store.dispatch(observeQuery(query));

    wrappedCallback = (uids) => {
      return callback(
        queryResultsToPath(
          uidsToResponse(uids, this._store.getState())
        )
      );
    }

    return storeToObserver(this._store).observe(pathInStore, wrappedCallback);
  }

  save(...args) {
    return dispatchThunkAndExpect(this._store, save(...args), types.SAVE_SUCCESSFUL);
  }

  // Editable
  editable(on) {
    this._store.dispatch(on ? editActive() : editInactive());
  }

  // State
  getState(substate) {
    let state = this._store.getState();

    if (substate) {
      let path = PUBLIC_STATE_MAP[substate];
      return path ? selectPropByPath(path, state) : undefined;
    }

    return Object.keys(PUBLIC_STATE_MAP).reduce((publicState, property) => {
      let path = PUBLIC_STATE_MAP[property];
      return Object.assign(
        publicState,
        { [ property ]: selectPropByPath(path, state) }
      );
    }, {});
  }

  observeState(substate, callback) {
    if (!substate) {
      throw new Error('No state given. Must include a state to observe e.g. Simpla.observeState(\'authenticated\', showAdmin)');
    }

    let realSubstate = PUBLIC_STATE_MAP[substate];
    return storeToObserver(this._store).observe(realSubstate, callback);
  }
}

// Init plugins
const plugins = [
  ping,
  persistToken
];

plugins.forEach(plugin => plugin(Simpla));

export default Simpla;
<commit_msg>Wrap results in clones to ensure dont leak references to internal state<commit_after>import 'es6-promise/auto';
import { createStore, applyMiddleware, compose } from 'redux';
import { setOption } from './actions/options';
import { editActive, editInactive } from './actions/editable';
import { login, logout } from './actions/authentication';
import { get, set, remove, find } from './actions/data';
import { observeQuery } from './actions/queries';
import save from './actions/save';
import { AUTH_SERVER } from './constants/options';
import { DATA_PREFIX, QUERIES_PREFIX, PUBLIC_STATE_MAP } from './constants/state';
import * as types from './constants/actionTypes';
import { configurePolymer } from './utils/prepare';
import {
  storeToObserver,
  dispatchThunkAndExpect,
  pathToUid,
  selectPropByPath,
  itemUidToPath,
  queryResultsToPath,
  validatePath,
  toQueryParams,
  uidsToResponse,
  clone
} from './utils/helpers';
import ping from './plugins/ping';
import persistToken from './plugins/persistToken';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

// Setup Polymer configuration
configurePolymer();

const Simpla = new class Simpla {
  constructor() {
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
    this._store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
  }

  init(project) {
    this._store.dispatch(setOption('project', project));

    // Initialize endpoints
    this._store.dispatch(setOption('authEndpoint', AUTH_SERVER));
    this._store.dispatch(setOption('dataEndpoint', `${AUTH_SERVER}/projects/${project}/content`));
  }

  // Authentication
  login(...args) {
    return dispatchThunkAndExpect(this._store, login(...args), types.LOGIN_SUCCESSFUL);
  }

  logout(...args) {
    return dispatchThunkAndExpect(this._store, logout(...args), types.LOGOUT_SUCCESSFUL);
  }

  // Data
  find(options = {}) {
    let parentPath = options.parent;
    options.parent = pathToUid(parentPath);
    return Promise.resolve()
      .then(() => validatePath(parentPath))
      .then(() => dispatchThunkAndExpect(
        this._store,
        find(options),
        types.FIND_DATA_SUCCESSFUL
      ))
      .then(queryResultsToPath)
      .then(clone);
  }

  get(path, ...args) {
    const uid = pathToUid(path);
    return Promise.resolve()
      .then(() => validatePath(path))
      .then(() => dispatchThunkAndExpect(
        this._store,
        get(uid, ...args),
        types.GET_DATA_SUCCESSFUL
      ))
      .then(itemUidToPath)
      .then(clone);
  }

  set(path, ...args) {
    const uid = pathToUid(path);
    return Promise.resolve()
      .then(() => validatePath(path))
      .then(() => dispatchThunkAndExpect(
        this._store,
        set(uid, ...args),
        types.SET_DATA_SUCCESSFUL
      ))
      .then(itemUidToPath);
  }

  remove(path, ...args) {
    const uid = pathToUid(path);
    return Promise.resolve()
      .then(() => validatePath(path))
      .then(() => dispatchThunkAndExpect(
        this._store,
        remove(uid, ...args),
        types.REMOVE_DATA_SUCCESSFUL
      ))
      .then(itemUidToPath);
  }

  observe(path, ...args) {
    let callback = args.pop(),
        uid = pathToUid(path),
        pathInState,
        wrappedCallback;

    if (!uid) {
      throw new Error('Observe must be given a valid path');
    }

    validatePath(path);

    pathInState = [ DATA_PREFIX, 'content', uid ];
    wrappedCallback = () => this.get(path).then(callback);

    return storeToObserver(this._store).observe(pathInState, wrappedCallback);
  }

  observeQuery(query, callback) {
    let queryString,
        pathInStore,
        wrappedCallback;

    // Clone so as to not affect given param
    query = Object.assign({}, query);

    query.parent = pathToUid(query.parent);
    queryString = toQueryParams(query);
    pathInStore = [ QUERIES_PREFIX, queryString, 'matches' ];

    this._store.dispatch(observeQuery(query));

    wrappedCallback = (uids) => {
      return callback(
        queryResultsToPath(
          clone(uidsToResponse(uids, this._store.getState()))
        )
      );
    }

    return storeToObserver(this._store).observe(pathInStore, wrappedCallback);
  }

  save(...args) {
    return dispatchThunkAndExpect(this._store, save(...args), types.SAVE_SUCCESSFUL);
  }

  // Editable
  editable(on) {
    this._store.dispatch(on ? editActive() : editInactive());
  }

  // State
  getState(substate) {
    let state = this._store.getState();

    if (substate) {
      let path = PUBLIC_STATE_MAP[substate];
      return path ? selectPropByPath(path, state) : undefined;
    }

    return Object.keys(PUBLIC_STATE_MAP).reduce((publicState, property) => {
      let path = PUBLIC_STATE_MAP[property];
      return Object.assign(
        publicState,
        { [ property ]: selectPropByPath(path, state) }
      );
    }, {});
  }

  observeState(substate, callback) {
    if (!substate) {
      throw new Error('No state given. Must include a state to observe e.g. Simpla.observeState(\'authenticated\', showAdmin)');
    }

    let realSubstate = PUBLIC_STATE_MAP[substate];
    return storeToObserver(this._store).observe(realSubstate, callback);
  }
}

// Init plugins
const plugins = [
  ping,
  persistToken
];

plugins.forEach(plugin => plugin(Simpla));

export default Simpla;
<|endoftext|>


<commit_before> module.exports = (function(){
  var request = require('superagent');

  var _sneeze = {};
  var config;

  var isFunction = function(functionToCheck) {
   var getType = {};
   return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
  }

  _sneeze._enabled = true;

  /*
    - Gets the browser type and version if available
  */
  _sneeze.browserWithVersion = (function(){
    if(typeof navigator == 'undefined' && typeof process != 'undefined'){
      return 'NodeJS';
    }else if(typeof navigator == 'undefined'){
      return 'No browser information available';
    }
    var ua= navigator.userAgent, tem,
    M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
    if(/trident/i.test(M[1])){
        tem=  /\brv[ :]+(\d+)/g.exec(ua) || [];
        return 'IE '+(tem[1] || '');
    }
    if(M[1]=== 'Chrome'){
        tem= ua.match(/\b(OPR|Edge)\/(\d+)/);
        if(tem!= null) return tem.slice(1).join(' ').replace('OPR', 'Opera');
    }
    M= M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
    if((tem= ua.match(/version\/(\d+)/i))!= null) M.splice(1, 1, tem[1]);
    return M.join(' ');
  })();

  _sneeze.configure = function(configuration){
    configuration = configuration || {};
    config = config || {};
    config.url = configuration.url || config.url || '/errors';
    config.data = configuration.data || config.data;
    // any other configurations can go here
  }

  // returns a shallow copy of the configuration
  _sneeze.getConfig = function(){
    var configuration;
    if(config){
      configuration = {};
      for(var attr in config){
        configuration[attr] = config[attr];
      }
    }
    return configuration;
  }

  _sneeze.getAdditionalData = function(error, options){
    var context = options.context || this;
    if(options.data && isFunction(options.data)){
      return options.data.apply(context, error);
    }
    return options.data;
  }

  // will return options, comparing the options with _config, and fills the missing pieces of the options hash with _config values
  // will return options if _config is not defined
  _sneeze.extendOptions = function(options){
    if(!config){
      this.configure();
    }
    for(var attrName in config){
      if(typeof options[attrName] == 'undefined'){
        options[attrName] = config[attrName];
      }
    }
  }

  /*
    - Accepts an error object and passes the error to the configured url via POST
    - Will attempt to post to the server as long as a valid response is returned
    - Will not attempt further logging if a request to the server returns an error
  */
  _sneeze.log = function(error, options){
    if(!this._enabled){
      return false;
    }

    options = options || {};

    this.extendOptions(options);

    var errorInfo = this.getErrorInfo(error);

    errorInfo.data = this.getAdditionalData(error, options);

    try{
      this.sendError(errorInfo, options);
    }catch(e){
      _sneeze.disable();
    }

    return true;
  }

  _sneeze.sendError = function(info, options){
    request.post(options.url)
      .send(info)
      .end(function(err, response){
        if(err){
          _sneeze.disable();
        }
      });
  }

  /*
    Parses an error for
      - the event that triggered the error
      - the source of the error (script)
      - the line number of the error
      - the column number of the error
      - the complete stack of the error

    Will return the parsed error and the user's browser and version
  */

  _sneeze.getErrorInfo = function(error){
    var errorInfo = {},
        stack = error.stack || '';

    errorInfo.message = error.message;
    errorInfo.stack = stack.trim();
    errorInfo.browser = this.browserWithVersion;
    if(typeof navigator != 'undefined'){
      error.device = navigator.userAgent ? navigator.userAgent : 'N/A';
    }else{
      error.device = 'N/A'
    }

    if((/chrome/i).test(this.browserWithVersion) || (/NodeJS/.test(this.browserWithVersion))){
      var pieces = stack.split('\n'),
          src = pieces[pieces.length-1].replace(/(\(*)(\)*)/, '').split(':'),
          stop = src.length-2,
          source = '';

      source = '';
      for(var i=0; i < stop; i++){
        source+=src[i];
      }

      errorInfo.event = source.split('(')[0].trim();
      errorInfo.source = source.split('(')[1].trim();
      errorInfo.lineno = parseInt(src[src.length-2].trim());
      errorInfo.colno = parseInt(src[src.length-1].trim().replace(')', ''));

    }else if((/firefox/i).test(this.browserWithVersion)){
      errorInfo.event = 'N/A';
      errorInfo.source = error.fileName;
      errorInfo.lineno = error.lineNumber;
      errorInfo.colno = error.columnNumber;
    }else if((/IE/.test(this.browserWithVersion)) || (/msie/.test(this.browserWithVersion))){
      errorInfo.message = error.description;
      errorInfo.event = 'N/A';
      errorInfo.source = 'N/A';
      errorInfo.lineno = 'N/A';
      errorInfo.colno = 'N/A';
    }else if((/safari/i).test(this.browserWithVersion)){
      errorInfo.event = 'N/A';
      errorInfo.source = error.source;
      errorInfo.lineno = error.lineno;
      errorInfo.colno = error.colno;
    }
    return errorInfo;
  }

  /*
    - Global watch for errors.
    - Does not receive errors from functions executed with sneeze.catch
    - accepts a callback function, but does not wait for sneeze to send the error to the server
  */

  _sneeze.listen = function(options){
    options = options || {};
    var self = this;
    if(typeof window != 'undefined'){
      window.onerror = function(message, source, lineno, colno, error){
        if(!error){
          error = { message: message, source: source, lineno: lineno, colno: colno, stack: 'N/A'};
        }
        self.processError(error, options);
      }
    }else{
      process.on('uncaughtException', function(error){
        self.processError(error, options);
      });
    }
  }

  _sneeze.processError = function(error, options){
    var context = options.context || this;
    this.log(error, options);
    if(options.onError){
      options.onError.apply(context, [error, options]);
    }
  }
  /*
    - Attempts a function and catches any errors.
    - Will execute a callback if passed, but does not wait for sneeze to send the error to the server
  */
  _sneeze.catch = function(fn, options){
    var self = this;

    self.wrapTryCatch(fn, options)();
  }

  _sneeze.wrapTryCatch = function(fn, options){
    var self = this;
    var context = options.context || this;

    return function(){
      var err = null;
      try{
        fn.apply(context, arguments)
      }catch(e){
        err = { e: e }
      }

      if(err){
        self.log(err.e, options)
        if(options.onError){
          options.onError.apply(context, [err.e, options]);
        }
      }
    }
  }

  _sneeze.enable = function(){
    this._enabled = true;
  }

  _sneeze.disable = function(){
    this._enabled = false;
  }

  /*
    - Setup default configuration
  */
  _sneeze.configure();

  return _sneeze;
})();<commit_msg>Error information will return device information<commit_after> module.exports = (function(){
  var request = require('superagent');

  var _sneeze = {};
  var config;

  var isFunction = function(functionToCheck) {
   var getType = {};
   return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
  }

  _sneeze._enabled = true;

  /*
    - Gets the browser type and version if available
  */
  _sneeze.browserWithVersion = (function(){
    if(typeof navigator == 'undefined' && typeof process != 'undefined'){
      return 'NodeJS';
    }else if(typeof navigator == 'undefined'){
      return 'No browser information available';
    }
    var ua= navigator.userAgent, tem,
    M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
    if(/trident/i.test(M[1])){
        tem=  /\brv[ :]+(\d+)/g.exec(ua) || [];
        return 'IE '+(tem[1] || '');
    }
    if(M[1]=== 'Chrome'){
        tem= ua.match(/\b(OPR|Edge)\/(\d+)/);
        if(tem!= null) return tem.slice(1).join(' ').replace('OPR', 'Opera');
    }
    M= M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
    if((tem= ua.match(/version\/(\d+)/i))!= null) M.splice(1, 1, tem[1]);
    return M.join(' ');
  })();

  _sneeze.configure = function(configuration){
    configuration = configuration || {};
    config = config || {};
    config.url = configuration.url || config.url || '/errors';
    config.data = configuration.data || config.data;
    // any other configurations can go here
  }

  // returns a shallow copy of the configuration
  _sneeze.getConfig = function(){
    var configuration;
    if(config){
      configuration = {};
      for(var attr in config){
        configuration[attr] = config[attr];
      }
    }
    return configuration;
  }

  _sneeze.getAdditionalData = function(error, options){
    var context = options.context || this;
    if(options.data && isFunction(options.data)){
      return options.data.apply(context, error);
    }
    return options.data;
  }

  // will return options, comparing the options with _config, and fills the missing pieces of the options hash with _config values
  // will return options if _config is not defined
  _sneeze.extendOptions = function(options){
    if(!config){
      this.configure();
    }
    for(var attrName in config){
      if(typeof options[attrName] == 'undefined'){
        options[attrName] = config[attrName];
      }
    }
  }

  /*
    - Accepts an error object and passes the error to the configured url via POST
    - Will attempt to post to the server as long as a valid response is returned
    - Will not attempt further logging if a request to the server returns an error
  */
  _sneeze.log = function(error, options){
    if(!this._enabled){
      return false;
    }

    options = options || {};

    this.extendOptions(options);

    var errorInfo = this.getErrorInfo(error);

    errorInfo.data = this.getAdditionalData(error, options);

    try{
      this.sendError(errorInfo, options);
    }catch(e){
      _sneeze.disable();
    }

    return true;
  }

  _sneeze.sendError = function(info, options){
    request.post(options.url)
      .send(info)
      .end(function(err, response){
        if(err){
          _sneeze.disable();
        }
      });
  }

  /*
    Parses an error for
      - the event that triggered the error
      - the source of the error (script)
      - the line number of the error
      - the column number of the error
      - the complete stack of the error

    Will return the parsed error and the user's browser and version
  */

  _sneeze.getErrorInfo = function(error){
    error = error || {};
    var errorInfo = {},
        stack = error.stack || '';

    errorInfo.message = error.message;
    errorInfo.stack = stack.trim();
    errorInfo.browser = this.browserWithVersion;
    if(typeof navigator != 'undefined'){
      errorInfo.device = navigator.userAgent ? navigator.userAgent : 'N/A';
    }else{
      errorInfo.device = 'N/A'
    }

    if((/chrome/i).test(this.browserWithVersion) || (/NodeJS/.test(this.browserWithVersion))){
      var pieces = stack.split('\n'),
          src = pieces[pieces.length-1].replace(/(\(*)(\)*)/, '').split(':'),
          stop = src.length-2,
          source = '';

      source = '';
      for(var i=0; i < stop; i++){
        source+=src[i];
      }

      errorInfo.event = source.split('(')[0].trim();
      errorInfo.source = source.split('(')[1].trim();
      errorInfo.lineno = parseInt(src[src.length-2].trim());
      errorInfo.colno = parseInt(src[src.length-1].trim().replace(')', ''));

    }else if((/firefox/i).test(this.browserWithVersion)){
      errorInfo.event = 'N/A';
      errorInfo.source = error.fileName;
      errorInfo.lineno = error.lineNumber;
      errorInfo.colno = error.columnNumber;
    }else if((/IE/.test(this.browserWithVersion)) || (/msie/.test(this.browserWithVersion))){
      errorInfo.message = error.description;
      errorInfo.event = 'N/A';
      errorInfo.source = 'N/A';
      errorInfo.lineno = 'N/A';
      errorInfo.colno = 'N/A';
    }else if((/safari/i).test(this.browserWithVersion)){
      errorInfo.event = 'N/A';
      errorInfo.source = error.source;
      errorInfo.lineno = error.lineno;
      errorInfo.colno = error.colno;
    }
    return errorInfo;
  }

  /*
    - Global watch for errors.
    - Does not receive errors from functions executed with sneeze.catch
    - accepts a callback function, but does not wait for sneeze to send the error to the server
  */

  _sneeze.listen = function(options){
    options = options || {};
    var self = this;
    if(typeof window != 'undefined'){
      window.onerror = function(message, source, lineno, colno, error){
        if(!error){
          error = { message: message, source: source, lineno: lineno, colno: colno, stack: 'N/A'};
        }
        self.processError(error, options);
      }
    }else{
      process.on('uncaughtException', function(error){
        self.processError(error, options);
      });
    }
  }

  _sneeze.processError = function(error, options){
    var context = options.context || this;
    this.log(error, options);
    if(options.onError){
      options.onError.apply(context, [error, options]);
    }
  }
  /*
    - Attempts a function and catches any errors.
    - Will execute a callback if passed, but does not wait for sneeze to send the error to the server
  */
  _sneeze.catch = function(fn, options){
    var self = this;

    self.wrapTryCatch(fn, options)();
  }

  _sneeze.wrapTryCatch = function(fn, options){
    var self = this;
    var context = options.context || this;

    return function(){
      var err = null;
      try{
        fn.apply(context, arguments)
      }catch(e){
        err = { e: e }
      }

      if(err){
        self.log(err.e, options)
        if(options.onError){
          options.onError.apply(context, [err.e, options]);
        }
      }
    }
  }

  _sneeze.enable = function(){
    this._enabled = true;
  }

  _sneeze.disable = function(){
    this._enabled = false;
  }

  /*
    - Setup default configuration
  */
  _sneeze.configure();

  return _sneeze;
})();
<|endoftext|>


<commit_before>// utilities to retrieve the grid capabilities.
var http = require('http'),
    htmlparser = require('htmlparser');

exports.available = function(options, callback) {
    http.get({
        host: options.host,
        path: '/grid/console',
        port: options.port || 4444
    }, function(res) {
        var data = '';

        if (res.statusCode !== 200) {
            return callback('server returned status code ' + res.statusCode);
        }

        res.on('data', function(c) {
            data += c;
        });

        res
            .on('end', function () { parseHtml(data, callback); })
            .on('error', callback);
    });
};

exports.parseHtml = parseHtml;

function parseHtml(data, callback) {
    var parser,
        handler = new htmlparser.DefaultHandler(function (error, dom) {
            if (error) {return callback(error);}
            var container = find(matchBrowserList, dom),
                browsers = find(matchBrowserItem, container),
                parsed = parseItems(browsers);
            callback(null, parsed);
        }, { verbose: false, ignoreWhitespace: true });

    parser = new htmlparser.Parser(handler);
    parser.parseComplete(data);
}

var regContent = /{\s*([^\s].*[^\s])\s*}/;
var regSplit = /\s*,\s*/;
var regPart = /^(.*)\s*=\s*(.*)$/;

function find(match, dom) {
    var children = [], c;
    if (Array.isArray(dom)) {
        dom.forEach(function(item) {
            var c = find(match, item);
            if (c.length) {
                c.forEach(function(i) {
                    children.push(i);
                });
            }
        });
    } else {
        if (dom.type === "tag" && match(dom)) {
            children.push(dom);
        }

        if (dom.children) {
            c = find(match, dom.children);
            if (c.length) {
                c.forEach(function(i) {
                    children.push(i);
                });
            }
        }
    }

    return children;
}

function matchBrowserList(node) {
    return node.name === 'div' && node.attribs && node.attribs.type === 'browsers';
}

function matchBrowserItem(node) {
    return node.name === 'img';
}

function parseItems(items) {
    var browsers = [];
    items.forEach(function (item) {
        var result = parseItem(item.attribs.title);
        if (result) {
            browsers.push(result);
        }
    });
    return browsers;
}

function parseItem(title) {
    if (!regContent.test(title)) {
        // this is not an available node
        return false;
    }
    var raw = regContent.exec(title)[1];
    var parts = raw.split(regSplit);
    var result = {};
    parts.forEach(function (part) {
        var parsed = regPart.exec(part);
        var key = parsed[1];
        var value = parsed[2];

        result[key] = value;
    });

    return result;
}
<commit_msg>Improve error handling for grid status.<commit_after>// utilities to retrieve the grid capabilities.
var http = require('http'),
    htmlparser = require('htmlparser');

exports.available = function(options, callback) {
    http.get({
        host: options.host,
        path: '/grid/console',
        port: options.port || 4444
    }, function(res) {
        var data = '';

        if (res.statusCode !== 200) {
            return callback('server returned status code ' + res.statusCode);
        }

        res.on('data', function(c) {
            data += c;
        });

        res
            .on('end', function () { parseHtml(data, callback); })
            .on('error', callback);
    });
};

exports.parseHtml = parseHtml;

function parseHtml(data, callback) {
    var parser,
        handler = new htmlparser.DefaultHandler(function (error, dom) {
            if (error) {return callback(error);}
            var container, browsers, parsed;
            try {
                container = find(matchBrowserList, dom);
                browsers = find(matchBrowserItem, container);
                parsed = parseItems(browsers);
            } catch (e) {
                return callback(e);
            }
            callback(null, parsed);
        }, { verbose: false, ignoreWhitespace: true });

    parser = new htmlparser.Parser(handler);
    parser.parseComplete(data);
}

var regContent = /{\s*([^\s].*[^\s])\s*}/;
var regSplit = /\s*,\s*/;
var regPart = /^(.*)\s*=\s*(.*)$/;

function find(match, dom) {
    var children = [], c;
    if (!dom) {return children;}
    if (Array.isArray(dom)) {
        dom.forEach(function(item) {
            var c = find(match, item);
            if (c.length) {
                c.forEach(function(i) {
                    children.push(i);
                });
            }
        });
    } else {
        if (dom.type === "tag" && match(dom)) {
            children.push(dom);
        }

        if (dom.children) {
            c = find(match, dom.children);
            if (c.length) {
                c.forEach(function(i) {
                    children.push(i);
                });
            }
        }
    }

    return children;
}

function matchBrowserList(node) {
    return node.name === 'div' && node.attribs && node.attribs.type === 'browsers';
}

function matchBrowserItem(node) {
    return node.name === 'img';
}

function parseItems(items) {
    var browsers = [];
    items.forEach(function (item) {
        var result = parseItem(item.attribs.title);
        if (result) {
            browsers.push(result);
        }
    });
    return browsers;
}

function parseItem(title) {
    if (!regContent.test(title)) {
        // this is not an available node
        return false;
    }
    var raw = regContent.exec(title)[1];
    var parts = raw.split(regSplit);
    var result = {};
    parts.forEach(function (part) {
        var parsed = regPart.exec(part);
        var key = parsed[1];
        var value = parsed[2];

        result[key] = value;
    });

    return result;
}
<|endoftext|>


<commit_before>
//
//  stoke.js - Stoke class
//
//  a brush Stroke from start to end
//

var GEN;

(function (GEN) {

  var Stroke = (function () {

    function Stroke(startX, startY, endX, endY, options) {
      this.startX = startX;
      this.startY = startY;
      this.endX = endX;
      this.endY = endY;
      this.options = options || {};
    }

    Stroke.prototype.draw = function(context) {
      this.reps = this.options.reps || 16;
      this.lineWidth = this.options.lineWidth || context.lineWidth;
      this.color = this.options.color || new GEN.Color(context.strokeStyle);
      this.shadeRange = this.options.shadeRange || 0.5;
      context.lineCap = "round";
      var color;
      for (var i=0; i<reps; i++) {
        context.StrokeStyle = (this.color.getRandomShade(0.8)).rgba();
        context.lineWidth = Math.floor( Math.max(1, (originalLineWidth/5 - originalLineWidth/8) * Math.random() + originalLineWidth/8 ) );
        radius = Math.floor ( this.radius + ( (originalLineWidth - context.lineWidth) * Math.random() - (originalLineWidth / 2 - context.lineWidth / 2) ) );
        mpVar = (this.radius - radius) / 4 + (originalLineWidth - context.lineWidth) / 4;
        x = this.x + Math.floor( mpVar * Math.random() - mpVar / 2 );
        y = this.y + Math.floor( mpVar * Math.random() - mpVar / 2 );
        context.beginPath();
        startAngle = 2*Math.PI * Math.random();
        endAngle = startAngle - Math.PI * Math.random();
        //startAngle = 0;
        //endAngle = 2*Math.PI;
        context.Stroke(x, y, radius, startAngle, endAngle, this.clockwise);
        //context.closePath();
        context.Stroke();
      }
      context.lineWidth = originalLineWidth;
      context.StrokeStyle = originalStrokeStyle;
    };

    return Stroke;

  })();

  GEN.Stroke = Stroke;

})(GEN || (GEN = {}));

<commit_msg>starting on draw trigonometry<commit_after>
//
//  stoke.js - Stoke class
//
//  a brush Stroke from start to end
//

var GEN;

(function (GEN) {

  var Stroke = (function () {

    function Stroke(startX, startY, endX, endY, options) {
      this.startX = startX;
      this.startY = startY;
      this.endX = endX;
      this.endY = endY;
      this.options = options || {};
    }

    Stroke.prototype.draw = function(context) {
      this.reps = this.options.reps || 16;
      this.lineWidth = this.options.lineWidth || context.lineWidth;
      this.color = this.options.color || new GEN.Color(context.strokeStyle);
      this.shadeRange = this.options.shadeRange || 0.5;
      context.lineCap = "round";
      var color;
      for (var i=0; i<reps; i++) {
        context.StrokeStyle = (this.color.getRandomShade(0.8)).rgba();
        context.lineWidth = Math.floor( Math.max(1, (this.lineWidth/5 - this.lineWidth/8) * Math.random() + this.lineWidth/8 ) );

        radius = Math.floor ( this.radius + ( (this.lineWidth - context.lineWidth) * Math.random() - (this.lineWidth / 2 - context.lineWidth / 2) ) );
        mpVar = (this.radius - radius) / 4 + (this.lineWidth - context.lineWidth) / 4;
        x = this.x + Math.floor( mpVar * Math.random() - mpVar / 2 );
        y = this.y + Math.floor( mpVar * Math.random() - mpVar / 2 );
        context.beginPath();
        startAngle = 2*Math.PI * Math.random();
        endAngle = startAngle - Math.PI * Math.random();
        //startAngle = 0;
        //endAngle = 2*Math.PI;
        context.Stroke(x, y, radius, startAngle, endAngle, this.clockwise);
        //context.closePath();
        context.Stroke();
      }
      context.lineWidth = this.lineWidth;
      context.StrokeStyle = originalStrokeStyle;
    };

    return Stroke;

  })();

  GEN.Stroke = Stroke;

})(GEN || (GEN = {}));

<|endoftext|>


<commit_before>import Promise from 'bluebird';
import debug from 'debug';
import EventEmitter from 'events';
import moment from 'moment';
import path from 'path';
import streamBuffers from 'stream-buffers';
import ArchieConverter from './archie-converter';
import SpreadsheetConverter from './spreadsheet-converter';
import State from './state';
import url from 'url';

const log = debug('google-drive-sync:syncer');

const mimeTypes = [
    'application/vnd.google-apps.spreadsheet',
    'application/vnd.google-apps.document',
];

const IGNORED_ERRORS = [429];

export default class Syncer {
    constructor(opts) {
        this.client = opts.client;
        this.fs = opts.fs;

        this.outputDirectories = opts.outputDirectory.split(',');

        if (!opts.state) {
            throw new Error(`must specify "state" option`);
        }

        this.state = new State(this.fs, opts.state);
        this.events = new EventEmitter();
    }

    sync() {
        this.events.emit('sync');

        return Promise.join(
            this.state.read(),
            this.client.authorize(), // TODO: re-use JWT tokens but handle expiry
            () => {
                return this.ensurePageTokenInState().then(this.fetchChanges);
            }
        );
    }

    fetchChanges = () => {
        return this.client
            .getChanges(this.state.getPageToken())
            .then(this.handleChanges)
            .then(this.fetchSpecialFiles)
            .catch(err => this.events.emit('error', err));
    };

    ensurePageTokenInState() {
        if (this.state.getPageToken()) {
            return Promise.resolve();
        } else {
            return this.client
                .getStartPageToken()
                .then(res => this.state.save({ pageToken: res.startPageToken - 1 }));
        }
    }

    on(...args) {
        this.events.on(...args);
    }

    handleChanges = list => {
        log('found changes', list.changes);

        const changes = list.changes.filter(
            i => !i.removed && mimeTypes.indexOf(i.file.mimeType) !== -1
        );

        log(`fetching ${changes.length}`);

        return Promise.map(changes, this.processChange, { concurrency: 3 })
            .then(() =>
                this.state.save({
                    pageToken: list.nextPageToken || list.newStartPageToken,
                    lastCheck: moment().format(),
                })
            )
            .then(state => this.events.emit('synced', state.data));
    };

    fetchSpecialFiles = () => {
        const ids = process.env.GDRIVE_ALWAYS_FETCH
            ? process.env.GDRIVE_ALWAYS_FETCH.split(',')
            : [];

        return Promise.map(
            ids,
            id => this.client.getFile(id).then(this.downloadFile),
            { concurrency: 1 }
        );
    };

    processChange = change => {
        return this.client
            .getFile(change.fileId)
            .then(file =>
                this.downloadFile(file).then(() =>
                    this.state.setFile(change.fileId, { change, file })
                )
            );
    };

    downloadFile = file => {
        let promise = null;

        switch (file.mimeType) {
            case 'application/vnd.google-apps.spreadsheet':
            case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
                promise = this.convertSpreadsheet(file).then(data => [
                    {
                        fileName: `${file.id}.json`,
                        data,
                        mimeType: 'application/json',
                    },
                ]);
                break;
            case 'application/vnd.google-apps.document':
                promise = this.convertDocument(file).then(results => [
                    {
                        fileName: `${file.id}.json`,
                        data: results.plain,
                        mimeType: 'application/json',
                    },
                    {
                        fileName: `${file.id}.styled.json`,
                        data: results.styled,
                        mimeType: 'application/json',
                    },
                    {
                        fileName: `${file.id}.aml`,
                        data: results.aml,
                        mimeType: 'text/plain',
                    },
                    {
                        fileName: `${file.id}.html`,
                        data: results.html,
                        mimeType: 'text/html',
                    },
                ]);

                break;
            default:
                throw new Error(`unknown mime type: ${file.mimeType}`);
        }

        const modification = this.getModification(file);

        return promise
            .then(results => {
                return Promise.each(results, ({ fileName, data, mimeType }) =>
                    this.save(fileName, {
                        title: file.name,
                        modification,
                        data,
                        mimeType,
                    })
                );
            })
            .catch(err => {
                console.error(file.webViewLink, err.message);
                this.events.emit('error', err);

                let isIgnored =
                    err.httpResponse &&
                    IGNORED_ERRORS.includes(err.httpResponse.statusCode);
                isIgnored = isIgnored || !!err.isArchieMLError;

                const preThrow = err.aml
                    ? this.save(`${file.id}.aml`, { data: err.aml })
                    : Promise.resolve();

                if (isIgnored) {
                    log('error ignored');
                    return preThrow;
                } else {
                    return preThrow.then(() => Promise.reject(err));
                }
            });
    };

    convertSpreadsheet(doc) {
        return new Promise((resolve, reject) => {
            this.client
                .getExportFileStream(
                    doc.id,
                    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
                )
                .catch(reject)
                .then(stream => {
                    const writable = new streamBuffers.WritableStreamBuffer();

                    stream
                        .on('end', () => {
                            log('end stream');
                            const buf = writable.getContents();
                            writable.end();
                            resolve(buf);
                        })
                        .on('error', reject)
                        .pipe(writable);
                });
        }).then(buf => SpreadsheetConverter.convert(buf.toString('binary')));
    }

    convertDocument(doc) {
        return this.client
            .exportFile(doc.id, 'text/html')
            .then(html =>
                Promise.props({
                    html,
                    plain: ArchieConverter.convert(html),
                    styled: ArchieConverter.convert(html, {
                        preserve_styles: ['bold', 'italic', 'underline'],
                    }),
                })
            )
            .then(({ plain, styled, html }) => ({
                plain: plain.result,
                styled: styled.result,
                aml: plain.aml,
                html,
            }));
    }

    getModification(doc) {
        const now = moment();
        const modified = moment(doc.modifiedTime);
        const lag = moment.duration(now.diff(modified));

        const user = doc.lastModifyingUser || doc.sharingUser || {};

        const result = {
            user: {
                name: user.displayName,
                email: user.emailAddress,
            },
            date: modified.format(),
            version: doc.version,
            downloadTime: now.format(),
            downloadLag: {
                ms: lag.valueOf(),
                human: lag.humanize(),
            },
        };

        const previous = this.state.getFile(doc.id);

        if (previous && previous.file && previous.file.version) {
            result.lastUpdate = {
                version: previous.file.version,
                changeTime: previous.change
                    ? moment(previous.change.time).format()
                    : null,
            };

            if (+previous.file.version && +doc.version) {
                result.versionDiff = +doc.version - +previous.file.version;
            }
        }

        return result;
    }

    save(fileName, data) {
        if (data === null) {
            // probably an operational error - don't write the file
            return Promise.resolve();
        }

        const body = fileName.endsWith('.json') ? JSON.stringify(data) : data.data;

        return Promise.map(this.outputDirectories, dir => {
            const filePath = path.join(dir, fileName);

            return this.fs
                .write(filePath, body, { mimeType: data.mimeType })
                .then(() => this.events.emit('saved', fileName, data));
        });
    }
}
<commit_msg>disable concurrent writes<commit_after>import Promise from 'bluebird';
import debug from 'debug';
import EventEmitter from 'events';
import moment from 'moment';
import path from 'path';
import streamBuffers from 'stream-buffers';
import ArchieConverter from './archie-converter';
import SpreadsheetConverter from './spreadsheet-converter';
import State from './state';
import url from 'url';

const log = debug('google-drive-sync:syncer');

const mimeTypes = [
    'application/vnd.google-apps.spreadsheet',
    'application/vnd.google-apps.document',
];

const IGNORED_ERRORS = [429];

export default class Syncer {
    constructor(opts) {
        this.client = opts.client;
        this.fs = opts.fs;

        this.outputDirectories = opts.outputDirectory.split(',');

        if (!opts.state) {
            throw new Error(`must specify "state" option`);
        }

        this.state = new State(this.fs, opts.state);
        this.events = new EventEmitter();
    }

    sync() {
        this.events.emit('sync');

        return Promise.join(
            this.state.read(),
            this.client.authorize(), // TODO: re-use JWT tokens but handle expiry
            () => {
                return this.ensurePageTokenInState().then(this.fetchChanges);
            }
        );
    }

    fetchChanges = () => {
        return this.client
            .getChanges(this.state.getPageToken())
            .then(this.handleChanges)
            .then(this.fetchSpecialFiles)
            .catch(err => this.events.emit('error', err));
    };

    ensurePageTokenInState() {
        if (this.state.getPageToken()) {
            return Promise.resolve();
        } else {
            return this.client
                .getStartPageToken()
                .then(res => this.state.save({ pageToken: res.startPageToken - 1 }));
        }
    }

    on(...args) {
        this.events.on(...args);
    }

    handleChanges = list => {
        log('found changes', list.changes);

        const changes = list.changes.filter(
            i => !i.removed && mimeTypes.indexOf(i.file.mimeType) !== -1
        );

        log(`fetching ${changes.length}`);

        return Promise.map(changes, this.processChange, { concurrency: 3 })
            .then(() =>
                this.state.save({
                    pageToken: list.nextPageToken || list.newStartPageToken,
                    lastCheck: moment().format(),
                })
            )
            .then(state => this.events.emit('synced', state.data));
    };

    fetchSpecialFiles = () => {
        const ids = process.env.GDRIVE_ALWAYS_FETCH
            ? process.env.GDRIVE_ALWAYS_FETCH.split(',')
            : [];

        return Promise.map(
            ids,
            id => this.client.getFile(id).then(this.downloadFile),
            { concurrency: 1 }
        );
    };

    processChange = change => {
        return this.client
            .getFile(change.fileId)
            .then(file =>
                this.downloadFile(file).then(() =>
                    this.state.setFile(change.fileId, { change, file })
                )
            );
    };

    downloadFile = file => {
        let promise = null;

        switch (file.mimeType) {
            case 'application/vnd.google-apps.spreadsheet':
            case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
                promise = this.convertSpreadsheet(file).then(data => [
                    {
                        fileName: `${file.id}.json`,
                        data,
                        mimeType: 'application/json',
                    },
                ]);
                break;
            case 'application/vnd.google-apps.document':
                promise = this.convertDocument(file).then(results => [
                    {
                        fileName: `${file.id}.json`,
                        data: results.plain,
                        mimeType: 'application/json',
                    },
                    {
                        fileName: `${file.id}.styled.json`,
                        data: results.styled,
                        mimeType: 'application/json',
                    },
                    {
                        fileName: `${file.id}.aml`,
                        data: results.aml,
                        mimeType: 'text/plain',
                    },
                    {
                        fileName: `${file.id}.html`,
                        data: results.html,
                        mimeType: 'text/html',
                    },
                ]);

                break;
            default:
                throw new Error(`unknown mime type: ${file.mimeType}`);
        }

        const modification = this.getModification(file);

        return promise
            .then(results => {
                return Promise.each(results, ({ fileName, data, mimeType }) =>
                    this.save(fileName, {
                        title: file.name,
                        modification,
                        data,
                        mimeType,
                    })
                );
            })
            .catch(err => {
                console.error(file.webViewLink, err.message);
                this.events.emit('error', err);

                let isIgnored =
                    err.httpResponse &&
                    IGNORED_ERRORS.includes(err.httpResponse.statusCode);
                isIgnored = isIgnored || !!err.isArchieMLError;

                const preThrow = err.aml
                    ? this.save(`${file.id}.aml`, { data: err.aml })
                    : Promise.resolve();

                if (isIgnored) {
                    log('error ignored');
                    return preThrow;
                } else {
                    return preThrow.then(() => Promise.reject(err));
                }
            });
    };

    convertSpreadsheet(doc) {
        return new Promise((resolve, reject) => {
            this.client
                .getExportFileStream(
                    doc.id,
                    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
                )
                .catch(reject)
                .then(stream => {
                    const writable = new streamBuffers.WritableStreamBuffer();

                    stream
                        .on('end', () => {
                            log('end stream');
                            const buf = writable.getContents();
                            writable.end();
                            resolve(buf);
                        })
                        .on('error', reject)
                        .pipe(writable);
                });
        }).then(buf => SpreadsheetConverter.convert(buf.toString('binary')));
    }

    convertDocument(doc) {
        return this.client
            .exportFile(doc.id, 'text/html')
            .then(html =>
                Promise.props({
                    html,
                    plain: ArchieConverter.convert(html),
                    styled: ArchieConverter.convert(html, {
                        preserve_styles: ['bold', 'italic', 'underline'],
                    }),
                })
            )
            .then(({ plain, styled, html }) => ({
                plain: plain.result,
                styled: styled.result,
                aml: plain.aml,
                html,
            }));
    }

    getModification(doc) {
        const now = moment();
        const modified = moment(doc.modifiedTime);
        const lag = moment.duration(now.diff(modified));

        const user = doc.lastModifyingUser || doc.sharingUser || {};

        const result = {
            user: {
                name: user.displayName,
                email: user.emailAddress,
            },
            date: modified.format(),
            version: doc.version,
            downloadTime: now.format(),
            downloadLag: {
                ms: lag.valueOf(),
                human: lag.humanize(),
            },
        };

        const previous = this.state.getFile(doc.id);

        if (previous && previous.file && previous.file.version) {
            result.lastUpdate = {
                version: previous.file.version,
                changeTime: previous.change
                    ? moment(previous.change.time).format()
                    : null,
            };

            if (+previous.file.version && +doc.version) {
                result.versionDiff = +doc.version - +previous.file.version;
            }
        }

        return result;
    }

    save(fileName, data) {
        if (data === null) {
            // probably an operational error - don't write the file
            return Promise.resolve();
        }

        const body = fileName.endsWith('.json') ? JSON.stringify(data) : data.data;

        return Promise.map(
            this.outputDirectories,
            dir => {
                const filePath = path.join(dir, fileName);

                return this.fs
                    .write(filePath, body, { mimeType: data.mimeType })
                    .then(() => this.events.emit('saved', fileName, data));
            },
            { concurrency: 1 }
        );
    }
}
<|endoftext|>