working websockets

This commit is contained in:
Wyatt Johnson
2017-03-29 09:35:56 -06:00
parent 0e0c3f19aa
commit ffbe976760
11 changed files with 123 additions and 59 deletions
+4 -35
View File
@@ -4,13 +4,11 @@ const morgan = require('morgan');
const path = require('path');
const helmet = require('helmet');
const passport = require('./services/passport');
const session = require('express-session');
const enabled = require('debug').enabled;
const RedisStore = require('connect-redis')(session);
const redis = require('./services/redis');
const csrf = require('csurf');
const errors = require('./errors');
const graph = require('./graph');
const session = require('./services/session');
const {createGraphOptions} = require('./graph');
const apollo = require('graphql-server-express');
const app = express();
@@ -42,34 +40,7 @@ app.set('view engine', 'ejs');
// SESSION MIDDLEWARE
//==============================================================================
const session_opts = {
secret: process.env.TALK_SESSION_SECRET,
httpOnly: true,
rolling: true,
saveUninitialized: true,
resave: true,
unset: 'destroy',
name: 'talk.sid',
cookie: {
secure: false,
maxAge: 8.64e+7, // 24 hours for session token expiry
},
store: new RedisStore({
client: redis.createClient(),
})
};
if (app.get('env') === 'production') {
// Enable the secure cookie when we are in production mode.
session_opts.cookie.secure = true;
} else if (app.get('env') === 'test') {
// Add in the secret during tests.
session_opts.secret = 'keyboard cat';
}
app.use(session(session_opts));
app.use(session);
//==============================================================================
// PASSPORT MIDDLEWARE
@@ -83,10 +54,8 @@ app.use(passport.session());
// GraphQL Router
//==============================================================================
graph.createSubscriptionManager(app, '/api/v1/live');
// GraphQL endpoint.
app.use('/api/v1/graph/ql', apollo.graphqlExpress(graph.createGraphOptions));
app.use('/api/v1/graph/ql', apollo.graphqlExpress(createGraphOptions));
// Only include the graphiql tool if we aren't in production mode.
if (app.get('env') !== 'production') {
+5 -3
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env node
const app = require('../app');
const program = require('./commander');
const app = require('../app');
const {createServer} = require('http');
const scraper = require('../services/scraper');
const mailer = require('../services/mailer');
const kue = require('../services/kue');
const mongoose = require('../services/mongoose');
const util = require('./util');
const {createSubscriptionManager} = require('../graph');
const subscriptions = require('../services/subscriptions');
/**
* Get port from environment and store in Express.
@@ -91,7 +91,9 @@ function startApp() {
* Listen on provided port, on all network interfaces.
*/
server.listen(port, () => {
createSubscriptionManager(server, '/api/v1/live');
// Mount the subscriptions server on the application server.
subscriptions.mount(server);
});
server.on('error', onError);
+18 -10
View File
@@ -3,6 +3,8 @@ import React from 'react';
import {graphql} from 'react-apollo';
import gql from 'graphql-tag';
import PubDate from 'coral-plugin-pubdate/PubDate';
class Embed extends React.Component {
static propTypes = {
@@ -20,18 +22,24 @@ class Embed extends React.Component {
}
render() {
console.log(this.props);
const {asset, loading} = this.props.data;
return (
<div>
<h1>Live Stream</h1>
<ul>
<h1>Wyatt's Awesome Live Stream</h1>
<div>
{!loading && asset && asset.comments && asset.comments.map((comment) => (
<li key={comment.id}>
<span>{comment.id} {comment.body}</span>
</li>
<div key={comment.id} style={{position: 'relative'}}>
<span>
<b>{comment.user.username}</b> - {comment.body}
</span>
<span
style={{float: 'right'}}
>
<PubDate created_at={comment.created_at} />
</span>
</div>
))}
</ul>
</div>
</div>
);
}
@@ -40,12 +48,13 @@ class Embed extends React.Component {
const COMMENT_QUERY = gql`
query Comment($assetID: ID!) {
asset(id: $assetID) {
comments {
comments(limit: 50) {
id
body
user {
username
}
created_at
}
}
}
@@ -59,6 +68,7 @@ const COMMENTS_SUBSCRIPTION = gql`
user {
username
}
created_at
}
}
`;
@@ -95,8 +105,6 @@ const withData = graphql(COMMENT_QUERY, {
}
};
console.log('updateQuery', before, after);
return after;
}
});
+1 -1
View File
@@ -10,7 +10,7 @@ import Embed from './Embed';
render(
<ApolloProvider client={client}>
<Embed assetID='8377903e-2601-47d9-af8a-1baab5651da9' />
<Embed assetID='7babeab7-4546-4b2d-a0cd-841bd4e32c05' />
</ApolloProvider>
, document.querySelector('#coralStream')
);
+1 -1
View File
@@ -2,7 +2,7 @@ import ApolloClient, {addTypename} from 'apollo-client';
import getNetworkInterface from './transport';
import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws';
const wsClient = new SubscriptionClient('ws://localhost:3001/api/v1/live', {
const wsClient = new SubscriptionClient('ws://localhost:3000/api/v1/live', {
reconnect: true
});
const networkInterface = getNetworkInterface();
+4
View File
@@ -1,5 +1,6 @@
const loaders = require('./loaders');
const mutators = require('./mutators');
const uuid = require('uuid');
const plugins = require('../plugins');
const debug = require('debug')('talk:graph:context');
@@ -33,6 +34,9 @@ const decorateContextPlugins = (context, contextPlugins) => contextPlugins.reduc
class Context {
constructor({user = null}, pubsub) {
// Generate a new context id for the request.
this.id = uuid.v4();
// Load the current logged in user to `user`, otherwise this'll be null.
if (user) {
this.user = user;
+14 -4
View File
@@ -19,7 +19,7 @@ module.exports = {
// the lifespan of this request.
context: new Context(req, pubsub)
}),
createSubscriptionManager: (server, path) => new SubscriptionServer({
createSubscriptionManager: (server, path, sessionFactory) => new SubscriptionServer({
subscriptionManager: new SubscriptionManager({
schema,
pubsub,
@@ -31,9 +31,19 @@ module.exports = {
}),
}
}),
// onConnect: (connectionParams, webSocket) => {
// console.log(webSocket.upgradeReq.headers);
// }
onSubscribe: (parsedMessage, baseParams, connection) => {
// Attach the context per request.
baseParams.context = () => sessionFactory(connection.upgradeReq)
.then((req) => new Context(req, pubsub))
.catch((err) => {
console.error(err);
return new Context({}, pubsub);
});
return baseParams;
}
}, {
server,
path
+4
View File
@@ -5,6 +5,10 @@ module.exports = new GraphQLScalarType({
name: 'Date',
description: 'Date represented as an ISO8601 string',
serialize(value) {
if (typeof value === 'string') {
return value;
}
return value.toISOString();
},
parseValue(value) {
+36
View File
@@ -0,0 +1,36 @@
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('./services/redis');
//==============================================================================
// SESSION MIDDLEWARE
//==============================================================================
const session_opts = {
secret: process.env.TALK_SESSION_SECRET,
httpOnly: true,
rolling: true,
saveUninitialized: true,
resave: true,
unset: 'destroy',
name: 'talk.sid',
cookie: {
secure: false,
maxAge: 8.64e+7, // 24 hours for session token expiry
},
store: new RedisStore({
client: redis.createClient(),
})
};
if (process.env.NODE_ENV === 'production') {
// Enable the secure cookie when we are in production mode.
session_opts.cookie.secure = true;
} else if (process.env.NODE_ENV === 'test') {
// Add in the secret during tests.
session_opts.secret = 'keyboard cat';
}
module.exports = session(session_opts);
+35
View File
@@ -0,0 +1,35 @@
const {createSubscriptionManager} = require('../graph');
const session = require('./session');
const passport = require('./passport');
module.exports = class Subscriptions {
static deserializeUser(req) {
return new Promise((resolve, reject) => {
session(req, {}, () => {
if ('session' in req && 'passport' in req.session && 'user' in req.session.passport) {
passport.deserializeUser(req.session.passport.user, (err, user) => {
if (err) {
return reject(err);
}
req.user = user;
return resolve(req);
});
} else {
resolve(req);
}
});
});
}
static mount(server) {
// Create the SubscriptionManager and mount it on the specified route with
// this deserializer.
createSubscriptionManager(server, '/api/v1/live', Subscriptions.deserializeUser);
}
};
+1 -5
View File
@@ -4251,10 +4251,6 @@ jsbn@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd"
jsdom-global@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/jsdom-global/-/jsdom-global-2.1.1.tgz#47d46fe77f6167baf5d34431d3bb59fc41b0915a"
jsdom@^7.0.2:
version "7.2.2"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-7.2.2.tgz#40b402770c2bda23469096bee91ab675e3b1fc6e"
@@ -4275,7 +4271,7 @@ jsdom@^7.0.2:
whatwg-url-compat "~0.6.5"
xml-name-validator ">= 2.0.1 < 3.0.0"
jsdom@^9.12.0:
jsdom@^9.8.3:
version "9.12.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4"
dependencies: