From 63ef49d59530802f4153f55bcbcc446d7a2aea8f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 23 Mar 2017 11:09:14 -0600 Subject: [PATCH 01/19] Initial subscription impl --- app.js | 2 + bin/cli-serve | 10 +- client/coral-embed-live-stream/src/Embed.js | 107 +++++ client/coral-embed-live-stream/src/index.js | 16 + client/coral-framework/services/client.js | 12 +- graph/context.js | 5 +- graph/index.js | 29 +- graph/mutators/comment.js | 5 +- graph/resolvers/index.js | 2 + graph/resolvers/subscription.js | 7 + graph/typeDefs.graphql | 9 + package.json | 41 +- routes/embed/index.js | 2 + services/redis.js | 50 ++- views/embed/live-stream.ejs | 10 + webpack.config.js | 3 +- yarn.lock | 458 ++++++++++++-------- 17 files changed, 533 insertions(+), 235 deletions(-) create mode 100644 client/coral-embed-live-stream/src/Embed.js create mode 100644 client/coral-embed-live-stream/src/index.js create mode 100644 graph/resolvers/subscription.js create mode 100644 views/embed/live-stream.ejs diff --git a/app.js b/app.js index 5b704bea0..2bf7e93fe 100644 --- a/app.js +++ b/app.js @@ -83,6 +83,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)); diff --git a/bin/cli-serve b/bin/cli-serve index 08b451904..ae5da3d3b 100755 --- a/bin/cli-serve +++ b/bin/cli-serve @@ -2,12 +2,13 @@ const app = require('../app'); const program = require('./commander'); -const http = require('http'); +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'); /** * Get port from environment and store in Express. @@ -20,7 +21,7 @@ app.set('port', port); /** * Create HTTP server. */ -const server = http.createServer(app); +const server = createServer(app); /** * Event listener for HTTP server "error" event. @@ -89,7 +90,10 @@ function startApp() { /** * Listen on provided port, on all network interfaces. */ - server.listen(port); + server.listen(port, () => { + createSubscriptionManager(server, '/api/v1/live'); + }); + server.on('error', onError); server.on('listening', onListening); } diff --git a/client/coral-embed-live-stream/src/Embed.js b/client/coral-embed-live-stream/src/Embed.js new file mode 100644 index 000000000..9301e2a21 --- /dev/null +++ b/client/coral-embed-live-stream/src/Embed.js @@ -0,0 +1,107 @@ +import React from 'react'; + +import {graphql} from 'react-apollo'; +import gql from 'graphql-tag'; + +class Embed extends React.Component { + + static propTypes = { + assetID: React.PropTypes.string.isRequired, + subscribeToNewComments: React.PropTypes.func.isRequired, + data: React.PropTypes.shape({ + loading: React.PropTypes.bool.isRequired, + asset: React.PropTypes.object, + }).isRequired + } + + componentWillMount() { + const {assetID} = this.props; + this.props.subscribeToNewComments({assetID}); + } + + render() { + console.log(this.props); + const {asset, loading} = this.props.data; + return ( +
+

Live Stream

+ +
+ ); + } +} + +const COMMENT_QUERY = gql` + query Comment($assetID: ID!) { + asset(id: $assetID) { + comments { + id + body + user { + username + } + } + } + } +`; + +const COMMENTS_SUBSCRIPTION = gql` + subscription onCommentAdded($assetID: ID!){ + commentAdded(asset_id: $assetID) { + id + body + user { + username + } + } + } +`; + +const withData = graphql(COMMENT_QUERY, { + options: ({assetID}) => ({ + variables: { + assetID + }, + }), + props: (props) => ({ + ...props, + subscribeToNewComments: ({assetID}) => { + return props.data.subscribeToMore({ + document: COMMENTS_SUBSCRIPTION, + variables: {assetID}, + updateQuery: (before, {subscriptionData}) => { + if (!subscriptionData.data) { + return before; + } + + const newComment = subscriptionData.data.commentAdded; + + if (!newComment) { + console.warn('last comment was null :/'); + return before; + } + + let after = { + ...before, + asset: { + ...before.asset, + comments: [newComment, ...before.asset.comments] + } + }; + + console.log('updateQuery', before, after); + + return after; + } + }); + } + }) +}); + +export default withData(Embed); diff --git a/client/coral-embed-live-stream/src/index.js b/client/coral-embed-live-stream/src/index.js new file mode 100644 index 000000000..c546ccd2e --- /dev/null +++ b/client/coral-embed-live-stream/src/index.js @@ -0,0 +1,16 @@ +import React from 'react'; +import {render} from 'react-dom'; +import {ApolloProvider} from 'react-apollo'; + +import {client} from 'coral-framework/services/client'; + +// import store from 'coral-framework/services/store'; + +import Embed from './Embed'; + +render( + + + + , document.querySelector('#coralStream') +); diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js index b4a7a38df..d1974df96 100644 --- a/client/coral-framework/services/client.js +++ b/client/coral-framework/services/client.js @@ -1,6 +1,16 @@ 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', { + reconnect: true +}); +const networkInterface = getNetworkInterface(); +const networkInterfaceWithSubscriptions = addGraphQLSubscriptions( + networkInterface, + wsClient, +); + export const client = new ApolloClient({ connectToDevTools: true, queryTransformer: addTypename, @@ -10,5 +20,5 @@ export const client = new ApolloClient({ } return null; }, - networkInterface: getNetworkInterface() + networkInterface: networkInterfaceWithSubscriptions, }); diff --git a/graph/context.js b/graph/context.js index 432ab2081..3d2e13991 100644 --- a/graph/context.js +++ b/graph/context.js @@ -31,7 +31,7 @@ const decorateContextPlugins = (context, contextPlugins) => contextPlugins.reduc * Stores the request context. */ class Context { - constructor({user = null}) { + constructor({user = null}, pubsub) { // Load the current logged in user to `user`, otherwise this'll be null. if (user) { @@ -46,6 +46,9 @@ class Context { // Decorate the plugin context. this.plugins = decorateContextPlugins(this, contextPlugins); + + // Bind the publish/subscribe to the context. + this.pubsub = pubsub; } } diff --git a/graph/index.js b/graph/index.js index 7fddce2eb..b690ec22c 100644 --- a/graph/index.js +++ b/graph/index.js @@ -1,7 +1,15 @@ const schema = require('./schema'); const Context = require('./context'); +const {connectionOptions} = require('../services/redis'); +const {RedisPubSub} = require('graphql-redis-subscriptions'); +const {SubscriptionManager} = require('graphql-subscriptions'); +const {SubscriptionServer} = require('subscriptions-transport-ws'); + +const pubsub = new RedisPubSub(connectionOptions); + module.exports = { + pubsub, createGraphOptions: (req) => ({ // Schema is created already, so just include it. @@ -9,6 +17,25 @@ module.exports = { // Load in the new context here, this'll create the loaders + mutators for // the lifespan of this request. - context: new Context(req) + context: new Context(req, pubsub) + }), + createSubscriptionManager: (server, path) => new SubscriptionServer({ + subscriptionManager: new SubscriptionManager({ + schema, + pubsub, + setupFunctions: { + commentAdded: (options, args) => ({ + commentAdded: { + filter: (comment) => comment.asset_id === args.asset_id + }, + }), + } + }), + // onConnect: (connectionParams, webSocket) => { + // console.log(webSocket.upgradeReq.headers); + // } + }, { + server, + path }) }; diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 728603ae2..97504883e 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -16,7 +16,7 @@ const Wordlist = require('../../services/wordlist'); * @param {String} [status='NONE'] the status of the new comment * @return {Promise} resolves to the created comment */ -const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = null}, status = 'NONE') => { +const createComment = ({user, loaders: {Comments}, pubsub}, {body, asset_id, parent_id = null}, status = 'NONE') => { let tags = []; if (user.hasRoles('ADMIN') || user.hasRoles('MODERATOR')) { @@ -44,6 +44,9 @@ const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = Comments.parentCountByAssetID.incr(asset_id); } Comments.countByAssetID.incr(asset_id); + + // Publish the newly added comment via the subscription. + pubsub.publish('commentAdded', comment); } return comment; diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index 7e165f2db..024a0395d 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -16,6 +16,7 @@ const LikeAction = require('./like_action'); const RootMutation = require('./root_mutation'); const RootQuery = require('./root_query'); const Settings = require('./settings'); +const Subscription = require('./subscription'); const UserError = require('./user_error'); const User = require('./user'); const ValidationUserError = require('./validation_user_error'); @@ -39,6 +40,7 @@ let resolvers = { RootMutation, RootQuery, Settings, + Subscription, UserError, User, ValidationUserError, diff --git a/graph/resolvers/subscription.js b/graph/resolvers/subscription.js new file mode 100644 index 000000000..b3f5e655c --- /dev/null +++ b/graph/resolvers/subscription.js @@ -0,0 +1,7 @@ +const Subscription = { + commentAdded(comment) { + return comment; + } +}; + +module.exports = Subscription; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index e656d0183..b89ab2e03 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -706,6 +706,14 @@ type RootMutation { removeCommentTag(id: ID!, tag: String!): RemoveCommentTagResponse } +################################################################################ +## Subscriptions +################################################################################ + +type Subscription { + commentAdded(asset_id: ID!): Comment +} + ################################################################################ ## Schema ################################################################################ @@ -713,4 +721,5 @@ type RootMutation { schema { query: RootQuery mutation: RootMutation + subscription: Subscription } diff --git a/package.json b/package.json index 909452231..adb8837b7 100644 --- a/package.json +++ b/package.json @@ -50,45 +50,47 @@ "homepage": "https://github.com/coralproject/talk#readme", "dependencies": { "bcrypt": "^1.0.2", - "body-parser": "^1.15.2", + "body-parser": "^1.17.1", "cli-table": "^0.3.1", "commander": "^2.9.0", "connect-redis": "^3.1.0", "csurf": "^1.9.0", - "dataloader": "^1.2.0", - "debug": "^2.2.0", + "dataloader": "^1.3.0", + "debug": "^2.6.3", "dotenv": "^4.0.0", - "ejs": "^2.5.2", + "ejs": "^2.5.6", "env-rewrite": "^1.0.2", - "express": "^4.14.0", - "express-session": "^1.14.2", - "gql-merge": "^0.0.4", + "express": "^4.15.2", + "express-session": "^1.15.1", "form-data": "^2.1.2", - "graphql": "^0.8.2", + "gql-merge": "^0.0.4", + "graphql": "^0.9.1", "graphql-errors": "^2.1.0", - "graphql-server-express": "^0.5.0", - "graphql-tools": "^0.9.0", - "helmet": "^3.1.0", - "inquirer": "^3.0.1", - "jsonwebtoken": "^7.1.9", + "graphql-redis-subscriptions": "^1.1.5", + "graphql-server-express": "^0.6.0", + "graphql-subscriptions": "^0.3.1", + "graphql-tools": "^0.10.1", + "helmet": "^3.5.0", + "inquirer": "^3.0.6", + "jsonwebtoken": "^7.3.0", "kue": "^0.11.5", "linkify-it": "^2.0.3", "lodash": "^4.16.6", "metascraper": "^1.0.6", "minimist": "^1.2.0", - "mongoose": "^4.6.5", - "morgan": "^1.7.0", - "natural": "^0.4.0", + "mongoose": "^4.9.1", + "morgan": "^1.8.1", + "natural": "^0.5.0", "node-fetch": "^1.6.3", "nodemailer": "^2.6.4", "parse-duration": "^0.1.1", "passport": "^0.3.2", "passport-facebook": "^2.1.1", "passport-local": "^1.0.0", - "react-apollo": "^0.10.0", + "react-apollo": "^1.0.0-rc.3", "react-recaptcha": "^2.2.6", - "redis": "^2.6.5", - "uuid": "^2.0.3" + "redis": "^2.7.1", + "uuid": "^3.0.1" }, "devDependencies": { "apollo-client": "^0.8.3", @@ -165,6 +167,7 @@ "regenerator": "^0.8.46", "selenium-standalone": "^5.11.2", "style-loader": "^0.13.1", + "subscriptions-transport-ws": "^0.5.5-alpha.0", "supertest": "^2.0.1", "timeago.js": "^2.0.3", "webpack": "^2.2.1" diff --git a/routes/embed/index.js b/routes/embed/index.js index efa563422..93731bee5 100644 --- a/routes/embed/index.js +++ b/routes/embed/index.js @@ -13,6 +13,8 @@ router.use('/:embed', (req, res, next) => { return res.render('embed/stream', {customCssUrl, data}); }); + case 'live-stream': + return res.render('embed/live-stream'); default: // will return a 404. diff --git a/services/redis.js b/services/redis.js index 9f67c34bb..d27303fca 100644 --- a/services/redis.js +++ b/services/redis.js @@ -2,31 +2,35 @@ const redis = require('redis'); const debug = require('debug')('talk:redis'); const url = process.env.TALK_REDIS_URL || 'redis://localhost'; +const connectionOptions = { + url, + retry_strategy: function(options) { + if (options.error && options.error.code === 'ECONNREFUSED') { + + // End reconnecting on a specific error and flush all commands with a individual error + return new Error('The server refused the connection'); + } + if (options.total_retry_time > 1000 * 60 * 60) { + + // End reconnecting after a specific timeout and flush all commands with a individual error + return new Error('Retry time exhausted'); + } + + if (options.times_connected > 10) { + + // End reconnecting with built in error + return undefined; + } + + // reconnect after + return Math.max(options.attempt * 100, 3000); + } +}; + module.exports = { + connectionOptions, createClient() { - let client = redis.createClient(url, { - retry_strategy: function(options) { - if (options.error && options.error.code === 'ECONNREFUSED') { - - // End reconnecting on a specific error and flush all commands with a individual error - return new Error('The server refused the connection'); - } - if (options.total_retry_time > 1000 * 60 * 60) { - - // End reconnecting after a specific timeout and flush all commands with a individual error - return new Error('Retry time exhausted'); - } - - if (options.times_connected > 10) { - - // End reconnecting with built in error - return undefined; - } - - // reconnect after - return Math.max(options.attempt * 100, 3000); - } - }); + let client = redis.createClient(connectionOptions); client.ping((err) => { if (err) { diff --git a/views/embed/live-stream.ejs b/views/embed/live-stream.ejs new file mode 100644 index 000000000..e10f408cb --- /dev/null +++ b/views/embed/live-stream.ejs @@ -0,0 +1,10 @@ + + + + Live Stream + + +
+ + + diff --git a/webpack.config.js b/webpack.config.js index 432bb6155..7f000df84 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,7 +13,8 @@ const buildTargets = [ ]; const buildEmbeds = [ - 'stream' + 'stream', + 'live-stream' ]; module.exports = { diff --git a/yarn.lock b/yarn.lock index ef0b4fa82..8f7116ad3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,6 +34,10 @@ version "0.0.30" resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.30.tgz#a21717624cde9a48c2db53a4e500fc5c32a99bbc" +"@types/isomorphic-fetch@0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.33.tgz#3ea1b86f8b73e6a7430d01d4dbd5b1f63fd72718" + "@types/mime@*": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-0.0.29.tgz#fbcfd330573b912ef59eeee14602bface630754b" @@ -49,6 +53,12 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/ws@0.0.38": + version "0.0.38" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-0.0.38.tgz#42106fff4b422ca956734e29f0d73a6d893194d3" + dependencies: + "@types/node" "*" + abab@^1.0.0, abab@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" @@ -189,6 +199,20 @@ apollo-client@^0.8.3: "@types/graphql" "^0.8.0" "@types/isomorphic-fetch" "0.0.30" +apollo-client@^1.0.0-rc.2: + version "1.0.0-rc.5" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.0-rc.5.tgz#862bbd72a267a998627009665b5581ed13e26010" + dependencies: + graphql-anywhere "^3.0.1" + graphql-tag "^1.3.1" + redux "^3.4.0" + symbol-observable "^1.0.2" + whatwg-fetch "^2.0.0" + optionalDependencies: + "@types/async" "^2.0.31" + "@types/graphql" "^0.8.0" + "@types/isomorphic-fetch" "0.0.33" + "apparatus@>= 0.0.9": version "0.0.9" resolved "https://registry.yarnpkg.com/apparatus/-/apparatus-0.0.9.tgz#37dcd25834ad0b651076596291db823eeb1908bd" @@ -323,7 +347,7 @@ async@1.x, async@^1.4.0, async@^1.4.2, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@2.1.4, async@^2.1.2, async@^2.1.4: +async@2.1.4, async@^2.0.1, async@^2.1.2, async@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" dependencies: @@ -1055,6 +1079,10 @@ babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: version "6.15.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" +backo2@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + balanced-match@0.1.0, balanced-match@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a" @@ -1079,9 +1107,9 @@ base64url@2.0.0, base64url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" -basic-auth@~1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.0.4.tgz#030935b01de7c9b94a824b29f3fccb750d3a5290" +basic-auth@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884" bcrypt-pbkdf@^1.0.0: version "1.0.0" @@ -1145,18 +1173,18 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" -body-parser@^1.12.2, body-parser@^1.15.2: - version "1.16.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.0.tgz#924a5e472c6229fb9d69b85a20d5f2532dec788b" +body-parser@^1.12.2, body-parser@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.1.tgz#75b3bc98ddd6e7e0d8ffe750dfaca5c66993fa47" dependencies: bytes "2.4.0" content-type "~1.0.2" - debug "2.6.0" + debug "2.6.1" depd "~1.1.0" - http-errors "~1.5.1" + http-errors "~1.6.1" iconv-lite "0.4.15" on-finished "~2.3.0" - qs "6.2.1" + qs "6.4.0" raw-body "~2.2.0" type-is "~1.6.14" @@ -1734,12 +1762,12 @@ connect-redis@^3.1.0: debug "^2.2.0" redis "^2.1.0" -connect@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.5.0.tgz#b357525a0b4c1f50599cd983e1d9efeea9677198" +connect@3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.0.tgz#f09a4f7dcd17324b663b725c815bdb1c4158a46e" dependencies: - debug "~2.2.0" - finalhandler "0.5.0" + debug "2.6.1" + finalhandler "1.0.0" parseurl "~1.3.1" utils-merge "1.0.0" @@ -2075,21 +2103,17 @@ data-uri-to-buffer@0: version "0.0.4" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-0.0.4.tgz#46e13ab9da8e309745c8d01ce547213ebdb2fe3f" -dataloader@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.2.0.tgz#3f73ea657c492c860c1633348adc55ca9bf2107e" - -date-format@^0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-0.0.0.tgz#09206863ab070eb459acea5542cbd856b11966b3" +dataloader@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.3.0.tgz#6fec5be4b30a712e4afd30b86b4334566b97673b" date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@*, debug@2, debug@2.6.0, debug@^2.1.1, debug@^2.2.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" +debug@*, debug@2, debug@^2.1.1, debug@^2.2.0, debug@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d" dependencies: ms "0.7.2" @@ -2105,7 +2129,13 @@ debug@2.3.3: dependencies: ms "0.7.2" -debug@^0.7.2, debug@~0.7.4: +debug@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" + dependencies: + ms "0.7.2" + +debug@~0.7.4: version "0.7.4" resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" @@ -2191,7 +2221,7 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@~1.1.0: +depd@1.1.0, depd@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" @@ -2328,9 +2358,9 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -ejs@^2.5.2: - version "2.5.5" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.5.tgz#6ef4e954ea7dcf54f66aad2fe7aa421932d9ed77" +ejs@^2.5.6: + version "2.5.6" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88" ejs@~0.8.3: version "0.8.8" @@ -2469,7 +2499,7 @@ es6-promise@3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4" -es6-promise@^3.0.2: +es6-promise@^3.0.2, es6-promise@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" @@ -2483,10 +2513,6 @@ es6-set@~0.1.3: es6-symbol "3" event-emitter "~0.3.4" -es6-shim@^0.35.3: - version "0.35.3" - resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.3.tgz#9bfb7363feffff87a6cdb6cd93e405ec3c4b6f26" - es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" @@ -2686,9 +2712,9 @@ esutils@^2.0.0, esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" -etag@~1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" +etag@~1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" event-emitter@~0.3.4: version "0.3.4" @@ -2709,6 +2735,10 @@ event-stream@~3.3.0: stream-combiner "~0.0.4" through "~2.3.1" +eventemitter3@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.2.tgz#20ce4891909ce9f35b088c94fab40e2c96f473ac" + events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -2742,23 +2772,23 @@ exports-loader@^0.6.3: loader-utils "0.2.x" source-map "0.1.x" -express-session@^1.14.2: - version "1.15.0" - resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.0.tgz#67131dd5b78a42bc57b50af0a14880265c03f919" +express-session@^1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.1.tgz#9abba15971beea7ad98da5a4d25ed92ba4a2984e" dependencies: cookie "0.3.1" cookie-signature "1.0.6" crc "3.4.4" - debug "2.6.0" + debug "2.6.1" depd "~1.1.0" on-headers "~1.0.1" parseurl "~1.3.1" uid-safe "~2.1.3" utils-merge "1.0.0" -express@^4.12.2, express@^4.14.0: - version "4.14.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.14.1.tgz#646c237f766f148c2120aff073817b9e4d7e0d33" +express@^4.12.2, express@^4.15.2: + version "4.15.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.15.2.tgz#af107fc148504457f2dca9a6f2571d7129b97b35" dependencies: accepts "~1.3.3" array-flatten "1.1.1" @@ -2766,23 +2796,25 @@ express@^4.12.2, express@^4.14.0: content-type "~1.0.2" cookie "0.3.1" cookie-signature "1.0.6" - debug "~2.2.0" + debug "2.6.1" depd "~1.1.0" encodeurl "~1.0.1" escape-html "~1.0.3" - etag "~1.7.0" - finalhandler "0.5.1" - fresh "0.3.0" + etag "~1.8.0" + finalhandler "~1.0.0" + fresh "0.5.0" merge-descriptors "1.0.1" methods "~1.1.2" on-finished "~2.3.0" parseurl "~1.3.1" path-to-regexp "0.1.7" proxy-addr "~1.1.3" - qs "6.2.0" + qs "6.4.0" range-parser "~1.2.0" - send "0.14.2" - serve-static "~1.11.2" + send "0.15.1" + serve-static "1.12.1" + setprototypeof "1.0.3" + statuses "~1.3.1" type-is "~1.6.14" utils-merge "1.0.0" vary "~1.1.0" @@ -2890,23 +2922,15 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" -finalhandler@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7" +finalhandler@1.0.0, finalhandler@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.0.tgz#b5691c2c0912092f18ac23e9416bde5cd7dc6755" dependencies: - debug "~2.2.0" - escape-html "~1.0.3" - on-finished "~2.3.0" - statuses "~1.3.0" - unpipe "~1.0.0" - -finalhandler@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.1.tgz#2c400d8d4530935bc232549c5fa385ec07de6fcd" - dependencies: - debug "~2.2.0" + debug "2.6.1" + encodeurl "~1.0.1" escape-html "~1.0.3" on-finished "~2.3.0" + parseurl "~1.3.1" statuses "~1.3.1" unpipe "~1.0.0" @@ -2999,9 +3023,9 @@ frameguard@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.0.0.tgz#7bcad469ee7b96e91d12ceb3959c78235a9272e9" -fresh@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f" +fresh@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e" from@~0: version "0.1.3" @@ -3337,10 +3361,14 @@ graceful-fs@~2.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -graphql-anywhere@^2.0.0, graphql-anywhere@^2.1.0: +graphql-anywhere@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-2.1.0.tgz#888c0a1718db3ff866b313070747777380560f69" +graphql-anywhere@^3.0.0, graphql-anywhere@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-3.0.1.tgz#73531db861174c8f212eafb9f8e84944b38b4e5a" + graphql-docs@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/graphql-docs/-/graphql-docs-0.2.0.tgz#cf803f9c9d354fa03e89073d74e419261a5bfa74" @@ -3356,35 +3384,57 @@ graphql-errors@^2.1.0: babel-runtime "^6.6.1" uuid "^2.0.2" -graphql-server-core@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/graphql-server-core/-/graphql-server-core-0.5.2.tgz#7e23fc516cb754e42c16f92928b595c354d6c8a7" +graphql-redis-subscriptions@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/graphql-redis-subscriptions/-/graphql-redis-subscriptions-1.1.5.tgz#69166bebd8d92243d6f8e80c97ebf4c588d80c8d" dependencies: - es6-shim "^0.35.3" + async "^2.0.1" + graphql-subscriptions "^0.2.0" + redis "^2.6.3" + +graphql-server-core@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/graphql-server-core/-/graphql-server-core-0.6.0.tgz#468625cba4a00f80275c065432faa481985f5a65" optionalDependencies: "@types/graphql" "^0.8.5" -graphql-server-express@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/graphql-server-express/-/graphql-server-express-0.5.2.tgz#c358110ddb2f82939b4d4c9d97ffb5afded7944a" +graphql-server-express@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/graphql-server-express/-/graphql-server-express-0.6.0.tgz#516090b4add82e6ed2c4a70dd2d925031fdcc289" dependencies: - graphql-server-core "^0.5.2" - graphql-server-module-graphiql "^0.5.2" + graphql-server-core "^0.6.0" + graphql-server-module-graphiql "^0.6.0" optionalDependencies: "@types/express" "^4.0.35" "@types/graphql" "^0.8.6" -graphql-server-module-graphiql@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/graphql-server-module-graphiql/-/graphql-server-module-graphiql-0.5.2.tgz#7e2a0c78b0267e784f8483ce5633810baf558dee" +graphql-server-module-graphiql@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/graphql-server-module-graphiql/-/graphql-server-module-graphiql-0.6.0.tgz#e37634b05f000731981e8ed13103f9a5861e5da0" -graphql-tag@^1.1.1, graphql-tag@^1.2.3: +graphql-subscriptions@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.2.3.tgz#75f9a02cb6d2b456b1703ce95670f793dacee607" + dependencies: + es6-promise "^3.2.1" + +graphql-subscriptions@^0.3.0, graphql-subscriptions@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-0.3.1.tgz#0cedc2d507420cf26cf414080b079f05402f0303" + dependencies: + es6-promise "^3.2.1" + +graphql-tag@^1.1.1, graphql-tag@^1.2.4, graphql-tag@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.3.2.tgz#7abb3a8fd9f3415d07163314ed237061c785b759" + +graphql-tag@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.2.3.tgz#74c62443fbf3e693647426d7359f7e3e6ce7dace" -graphql-tools@^0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-0.9.2.tgz#c3f42d0b78d2d6c57cea5ef2894863de34af9a11" +graphql-tools@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-0.10.1.tgz#274aa338d50b1c0b3ed6936eafd8ed3a19ed1828" dependencies: deprecated-decorator "^0.1.6" lodash "^4.3.0" @@ -3398,11 +3448,11 @@ graphql@^0.7.2: dependencies: iterall "1.0.2" -graphql@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.8.2.tgz#eb1bb524b38104bbf2c9157f9abc67db2feba7d2" +graphql@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.9.1.tgz#f4d154cbec054d4a5d3b1be95f23435c09aa86c8" dependencies: - iterall "1.0.2" + iterall "1.0.3" growl@1.8.1: version "1.8.1" @@ -3470,9 +3520,9 @@ hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" -helmet-csp@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.3.0.tgz#bc341939dfef5266cc817abcf53f079f61fe7e3f" +helmet-csp@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.4.0.tgz#7e53a157167a0645aadd7177d12ae6c605c1842e" dependencies: camelize "1.0.0" content-security-policy-builder "1.1.0" @@ -3480,15 +3530,15 @@ helmet-csp@2.3.0: lodash.reduce "4.6.0" platform "1.3.3" -helmet@^3.1.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.4.0.tgz#05a9437486b05ca219ed8d21dc5e3b6ec0c18118" +helmet@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.5.0.tgz#e1d6de27d2e3317d3182e00d672df3d0e1e12539" dependencies: - connect "3.5.0" + connect "3.6.0" dns-prefetch-control "0.1.0" dont-sniff-mimetype "1.0.0" frameguard "3.0.0" - helmet-csp "2.3.0" + helmet-csp "2.4.0" hide-powered-by "1.0.0" hpkp "2.0.0" hsts "2.0.0" @@ -3531,9 +3581,9 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -hooks-fixed@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/hooks-fixed/-/hooks-fixed-1.2.0.tgz#0d2772d4d7d685ff9244724a9f0b5b2559aac96b" +hooks-fixed@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hooks-fixed/-/hooks-fixed-2.0.0.tgz#a01d894d52ac7f6599bbb1f63dfc9c411df70cba" hosted-git-info@^2.1.4: version "2.1.5" @@ -3584,7 +3634,7 @@ htmlparser2@~3.8.1: entities "1.0" readable-stream "1.1" -http-errors@~1.5.0, http-errors@~1.5.1: +http-errors@~1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" dependencies: @@ -3592,6 +3642,15 @@ http-errors@~1.5.0, http-errors@~1.5.1: setprototypeof "1.0.2" statuses ">= 1.3.1 < 2" +http-errors@~1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" + dependencies: + depd "1.1.0" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + http-proxy-agent@1: version "1.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz#cc1ce38e453bf984a0f7702d2dd59c73d081284a" @@ -3764,9 +3823,9 @@ inquirer@0.8.2: rx "^2.4.3" through "^2.3.6" -inquirer@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.1.tgz#6dfbffaf4d697dd76c8fe349f919de01c28afc4d" +inquirer@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.6.tgz#e04aaa9d05b7a3cb9b0f407d04375f0447190347" dependencies: ansi-escapes "^1.1.0" chalk "^1.0.0" @@ -4143,6 +4202,10 @@ iterall@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.0.2.tgz#41a2e96ce9eda5e61c767ee5dc312373bb046e91" +iterall@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.0.3.tgz#e0b31958f835013c323ff0b10943829ac69aa4b7" + jade@0.26.3: version "0.26.3" resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" @@ -4284,9 +4347,9 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" -jsonwebtoken@^7.1.9: - version "7.2.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.2.1.tgz#0fc7217473fc02b4c9aa1e188aa70b51bba4fccb" +jsonwebtoken@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.3.0.tgz#85118d6a70e3fccdf14389f4e7a1c3f9c8a9fbba" dependencies: joi "^6.10.1" jws "^3.1.4" @@ -4333,9 +4396,9 @@ jws@^3.1.4: jwa "^1.1.4" safe-buffer "^5.0.1" -kareem@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/kareem/-/kareem-1.2.0.tgz#59851e833feb1ce6cf60000e0c23acf75c8a3547" +kareem@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-1.2.1.tgz#acdb8c8119845834abbfa58ade1cf9dea63dc752" keymaster@^1.6.2: version "1.6.2" @@ -4641,6 +4704,10 @@ lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.keys@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" @@ -4711,14 +4778,6 @@ lodash@^4.0.0, lodash@^4.1.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.6, lo version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -log4js@*: - version "1.1.0" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-1.1.0.tgz#c7d2b616d91bbf47cc65fb79d6fe04581c8096fa" - dependencies: - debug "^2.2.0" - semver "^5.3.0" - streamroller "^0.2.1" - longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -4972,44 +5031,44 @@ moment@2.x.x, moment@^2.10.3: version "2.17.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" -mongodb-core@2.1.7: - version "2.1.7" - resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.7.tgz#6a27909b98142ef2508d924c274969008954fa29" +mongodb-core@2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.9.tgz#85aa71ee4fb716196e06b787557bf139f801daf5" dependencies: bson "~1.0.4" require_optional "~1.0.0" -mongodb@2.2.22: - version "2.2.22" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.22.tgz#d67c588fc08f922db19754b1d2e03e2d7d1319fb" +mongodb@2.2.25: + version "2.2.25" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.25.tgz#d3b25dad00eda2bdfcbc996210ba082ac686a6b6" dependencies: es6-promise "3.2.1" - mongodb-core "2.1.7" + mongodb-core "2.1.9" readable-stream "2.1.5" -mongoose@^4.6.5: - version "4.8.1" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.8.1.tgz#1dbd3014fb39e9344271a53ec5dd741205864aae" +mongoose@^4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.9.1.tgz#e621d9e7356f46d1e39980a71063857405fa9099" dependencies: async "2.1.4" bson "~1.0.4" - hooks-fixed "1.2.0" - kareem "1.2.0" - mongodb "2.2.22" + hooks-fixed "2.0.0" + kareem "1.2.1" + mongodb "2.2.25" mpath "0.2.1" mpromise "0.5.5" - mquery "2.2.1" + mquery "2.3.0" ms "0.7.2" - muri "1.2.0" + muri "1.2.1" regexp-clone "0.0.1" sliced "1.0.1" -morgan@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.7.0.tgz#eb10ca8e50d1abe0f8d3dad5c0201d052d981c62" +morgan@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.8.1.tgz#f93023d3887bd27b78dfd6023cea7892ee27a4b1" dependencies: - basic-auth "~1.0.3" - debug "~2.2.0" + basic-auth "~1.1.0" + debug "2.6.1" depd "~1.1.0" on-finished "~2.3.0" on-headers "~1.0.1" @@ -5022,9 +5081,9 @@ mpromise@0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mpromise/-/mpromise-0.5.5.tgz#f5b24259d763acc2257b0a0c8c6d866fd51732e6" -mquery@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/mquery/-/mquery-2.2.1.tgz#aa31076419adce2b06e9757f4cfc5d7f371b47ce" +mquery@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-2.3.0.tgz#3d1717ad8958d0c99e42ea2461a109f3e5f3e458" dependencies: bluebird "2.10.2" debug "2.2.0" @@ -5039,9 +5098,9 @@ ms@0.7.2, ms@^0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" -muri@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/muri/-/muri-1.2.0.tgz#b86383c902920b09ebe62af0e75c94de5f33cd3d" +muri@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/muri/-/muri-1.2.1.tgz#ec7ea5ce6ca6a523eb1ab35bacda5fa816c9aa3c" mute-stream@0.0.4: version "0.0.4" @@ -5071,12 +5130,11 @@ natural@^0.2.0: sylvester ">= 0.0.12" underscore ">=1.3.1" -natural@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/natural/-/natural-0.4.0.tgz#3eb692d956a76ff05f4a379a277d455333906764" +natural@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/natural/-/natural-0.5.0.tgz#55a9bb68eccf5ece5535486004a57de264ae3180" dependencies: apparatus ">= 0.0.9" - log4js "*" sylvester ">= 0.0.12" underscore ">=1.3.1" @@ -5424,6 +5482,10 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" +options@>=0.0.5: + version "0.0.6" + resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" + os-browserify@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" @@ -6351,13 +6413,9 @@ q@^1.1.2: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" -qs@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" - -qs@6.2.1, qs@^6.1.0, qs@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" +qs@6.4.0, qs@^6.1.0, qs@^6.2.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" qs@~6.3.0: version "6.3.0" @@ -6430,11 +6488,13 @@ react-addons-test-utils@15.3.2: version "15.3.2" resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.3.2.tgz#c09a44f583425a4a9c1b38444d7a6c3e6f0f41f6" -react-apollo@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-0.10.1.tgz#97fd50855f8575672aa68330b9c64a201cd13343" +react-apollo@^1.0.0-rc.3: + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-1.0.0-rc.3.tgz#a1d613785d1f07dcf012d7cde4f2c3a0507ceb03" dependencies: - graphql-anywhere "^2.0.0" + apollo-client "^1.0.0-rc.2" + graphql-anywhere "^3.0.0" + graphql-tag "^1.3.1" hoist-non-react-statics "^1.2.0" invariant "^2.2.1" lodash.flatten "^4.2.0" @@ -6547,7 +6607,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@1.1, readable-stream@1.1.x, readable-stream@^1.1.7: +readable-stream@1.1, readable-stream@1.1.x: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" dependencies: @@ -6632,15 +6692,23 @@ redis-commands@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" -redis-parser@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.4.0.tgz#018ea743077aae944d0b798b2fd12587320bf3c9" +redis-parser@^2.0.0, redis-parser@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.5.0.tgz#79fc2b1d4a6e4d2870b35368433639271fca2617" redis@^0.12.1: version "0.12.1" resolved "https://registry.yarnpkg.com/redis/-/redis-0.12.1.tgz#64df76ad0fc8acebaebd2a0645e8a48fac49185e" -redis@^2.1.0, redis@^2.6.5, redis@~2.6.0-2: +redis@^2.1.0, redis@^2.6.3, redis@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/redis/-/redis-2.7.1.tgz#7d56f7875b98b20410b71539f1d878ed58ebf46a" + dependencies: + double-ended-queue "^2.1.0-0" + redis-commands "^1.2.0" + redis-parser "^2.5.0" + +redis@~2.6.0-2: version "2.6.5" resolved "https://registry.yarnpkg.com/redis/-/redis-2.6.5.tgz#87c1eff4a489f94b70871f3d08b6988f23a95687" dependencies: @@ -6964,32 +7032,32 @@ semver@~5.0.1: version "5.0.3" resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" -send@0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef" +send@0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.15.1.tgz#8a02354c26e6f5cca700065f5f0cdeba90ec7b5f" dependencies: - debug "~2.2.0" + debug "2.6.1" depd "~1.1.0" destroy "~1.0.4" encodeurl "~1.0.1" escape-html "~1.0.3" - etag "~1.7.0" - fresh "0.3.0" - http-errors "~1.5.1" + etag "~1.8.0" + fresh "0.5.0" + http-errors "~1.6.1" mime "1.3.4" ms "0.7.2" on-finished "~2.3.0" range-parser "~1.2.0" statuses "~1.3.1" -serve-static@~1.11.2: - version "1.11.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7" +serve-static@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.1.tgz#7443a965e3ced647aceb5639fa06bf4d1bbe0039" dependencies: encodeurl "~1.0.1" escape-html "~1.0.3" parseurl "~1.3.1" - send "0.14.2" + send "0.15.1" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -7007,6 +7075,10 @@ setprototypeof@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + sha.js@^2.3.6: version "2.4.8" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" @@ -7191,7 +7263,7 @@ stable@~0.1.3: version "0.1.5" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.5.tgz#08232f60c732e9890784b5bed0734f8b32a887b9" -"statuses@>= 1.3.1 < 2", statuses@~1.3.0, statuses@~1.3.1: +"statuses@>= 1.3.1 < 2", statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -7232,14 +7304,6 @@ stream-to@~0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/stream-to/-/stream-to-0.2.2.tgz#84306098d85fdb990b9fa300b1b3ccf55e8ef01d" -streamroller@^0.2.1: - version "0.2.2" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.2.2.tgz#a13420e04169e573db068f5920ee23d881abfe33" - dependencies: - date-format "^0.0.0" - debug "^0.7.2" - readable-stream "^1.1.7" - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -7332,6 +7396,19 @@ stylus@0.54.5, stylus@~0.54.5: sax "0.5.x" source-map "0.1.x" +subscriptions-transport-ws@^0.5.5-alpha.0: + version "0.5.5-alpha.0" + resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.5.5-alpha.0.tgz#f7ac4f8889fa032fa44f9d68459d552e3af41c7c" + dependencies: + "@types/ws" "0.0.38" + backo2 "^1.0.2" + eventemitter3 "^2.0.2" + graphql-subscriptions "^0.3.0" + graphql-tag "^1.2.4" + lodash.isobject "^3.0.2" + lodash.isstring "^4.0.1" + ws "^1.1.0" + sugarss@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-0.2.0.tgz#ac34237563327c6ff897b64742bf6aec190ad39e" @@ -7663,6 +7740,10 @@ uid2@0.0.x: version "0.0.3" resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" +ultron@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" + undefsafe@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f" @@ -7953,6 +8034,13 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +ws@^1.1.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61" + dependencies: + options ">=0.0.5" + ultron "1.0.x" + x-xss-protection@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.0.0.tgz#898afb93869b24661cf9c52f9ee8db8ed0764dd9" From 0e0c3f19aa623e1fa0a54daecf82f4390b55a73c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 23 Mar 2017 11:11:57 -0600 Subject: [PATCH 02/19] Fixed tests --- graph/mutators/comment.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 97504883e..c1aa0773f 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -45,8 +45,11 @@ const createComment = ({user, loaders: {Comments}, pubsub}, {body, asset_id, par } Comments.countByAssetID.incr(asset_id); - // Publish the newly added comment via the subscription. - pubsub.publish('commentAdded', comment); + if (pubsub) { + + // Publish the newly added comment via the subscription. + pubsub.publish('commentAdded', comment); + } } return comment; From ffbe976760d91775080816d34909aba5eaf421c4 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 29 Mar 2017 09:35:56 -0600 Subject: [PATCH 03/19] working websockets --- app.js | 39 +++------------------ bin/cli-serve | 8 +++-- client/coral-embed-live-stream/src/Embed.js | 28 +++++++++------ client/coral-embed-live-stream/src/index.js | 2 +- client/coral-framework/services/client.js | 2 +- graph/context.js | 4 +++ graph/index.js | 18 +++++++--- graph/resolvers/date.js | 4 +++ services/session.js | 36 +++++++++++++++++++ services/subscriptions.js | 35 ++++++++++++++++++ yarn.lock | 6 +--- 11 files changed, 123 insertions(+), 59 deletions(-) create mode 100644 services/session.js create mode 100644 services/subscriptions.js diff --git a/app.js b/app.js index 2bf7e93fe..2d679b0f7 100644 --- a/app.js +++ b/app.js @@ -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') { diff --git a/bin/cli-serve b/bin/cli-serve index ae5da3d3b..eef6526bd 100755 --- a/bin/cli-serve +++ b/bin/cli-serve @@ -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); diff --git a/client/coral-embed-live-stream/src/Embed.js b/client/coral-embed-live-stream/src/Embed.js index 9301e2a21..6be149007 100644 --- a/client/coral-embed-live-stream/src/Embed.js +++ b/client/coral-embed-live-stream/src/Embed.js @@ -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 (
-

Live Stream

-
    +

    Wyatt's Awesome Live Stream

    +
    {!loading && asset && asset.comments && asset.comments.map((comment) => ( -
  • - {comment.id} {comment.body} -
  • +
    + + {comment.user.username} - {comment.body} + + + + +
    ))} -
+
); } @@ -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; } }); diff --git a/client/coral-embed-live-stream/src/index.js b/client/coral-embed-live-stream/src/index.js index c546ccd2e..564ea0ed6 100644 --- a/client/coral-embed-live-stream/src/index.js +++ b/client/coral-embed-live-stream/src/index.js @@ -10,7 +10,7 @@ import Embed from './Embed'; render( - + , document.querySelector('#coralStream') ); diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js index d1974df96..7744ea9bd 100644 --- a/client/coral-framework/services/client.js +++ b/client/coral-framework/services/client.js @@ -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(); diff --git a/graph/context.js b/graph/context.js index 3d2e13991..ce66c4bcd 100644 --- a/graph/context.js +++ b/graph/context.js @@ -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; diff --git a/graph/index.js b/graph/index.js index b690ec22c..78d9edc9a 100644 --- a/graph/index.js +++ b/graph/index.js @@ -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 diff --git a/graph/resolvers/date.js b/graph/resolvers/date.js index 2335b9713..af79e3eb4 100644 --- a/graph/resolvers/date.js +++ b/graph/resolvers/date.js @@ -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) { diff --git a/services/session.js b/services/session.js new file mode 100644 index 000000000..5697e026d --- /dev/null +++ b/services/session.js @@ -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); diff --git a/services/subscriptions.js b/services/subscriptions.js new file mode 100644 index 000000000..b24d6d69b --- /dev/null +++ b/services/subscriptions.js @@ -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); + } +}; diff --git a/yarn.lock b/yarn.lock index 8f7116ad3..9da6badee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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: From dd3d1e9fe7418e1a8765724910074ab7a8df2a7e Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 29 Mar 2017 12:45:57 -0600 Subject: [PATCH 04/19] fix for session service --- services/session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/session.js b/services/session.js index 5697e026d..505dfdf4a 100644 --- a/services/session.js +++ b/services/session.js @@ -1,6 +1,6 @@ const session = require('express-session'); const RedisStore = require('connect-redis')(session); -const redis = require('./services/redis'); +const redis = require('./redis'); //============================================================================== // SESSION MIDDLEWARE From 75c88df8f18433f150db068907c6ef61b1deaabd Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 4 Apr 2017 10:12:25 -0600 Subject: [PATCH 05/19] Fix for build --- client/coral-embed-live-stream/style/default.css | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/coral-embed-live-stream/style/default.css diff --git a/client/coral-embed-live-stream/style/default.css b/client/coral-embed-live-stream/style/default.css new file mode 100644 index 000000000..e69de29bb From 48b7a676650c0e1fdc1ea3f2ea18fd26824d1e88 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 5 Apr 2017 11:24:52 -0400 Subject: [PATCH 06/19] Add comments --- services/subscriptions.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/subscriptions.js b/services/subscriptions.js index b24d6d69b..eaf2f9b23 100644 --- a/services/subscriptions.js +++ b/services/subscriptions.js @@ -4,24 +4,27 @@ const passport = require('./passport'); module.exports = class Subscriptions { + // Session data does not automatically attach to websocket req objects. + // This middleware code looks for a user in the session and, if it exists, + // attaches it to the graph req. 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); } - + }); }); } From 1fecd4589b76109247457a0fda42972aab1540db Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 5 Apr 2017 09:55:15 -0600 Subject: [PATCH 07/19] Removed unused file --- views/embed-stream.ejs | 48 ------------------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 views/embed-stream.ejs diff --git a/views/embed-stream.ejs b/views/embed-stream.ejs deleted file mode 100644 index c69773bcb..000000000 --- a/views/embed-stream.ejs +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - Talk - Coral Admin - - - - - - -
- - - - From b3582025b7f352c72bad064332d010ad43561349 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 5 Apr 2017 13:37:32 -0600 Subject: [PATCH 08/19] Fixed import --- app.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app.js b/app.js index 59a7d3acc..a0a5b7789 100644 --- a/app.js +++ b/app.js @@ -5,7 +5,6 @@ const path = require('path'); const helmet = require('helmet'); const {passport} = require('./services/passport'); const plugins = require('./services/plugins'); -const session = require('express-session'); const enabled = require('debug').enabled; const csrf = require('csurf'); const errors = require('./errors'); From 350d9e74f237230312a434db3e6b81ff43891356 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 5 Apr 2017 16:56:32 -0400 Subject: [PATCH 09/19] Get plugin setupFuncitons for subscriptions --- graph/index.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/graph/index.js b/graph/index.js index 78d9edc9a..021770d36 100644 --- a/graph/index.js +++ b/graph/index.js @@ -1,13 +1,32 @@ const schema = require('./schema'); const Context = require('./context'); +const plugins = require('../services/plugins'); const {connectionOptions} = require('../services/redis'); const {RedisPubSub} = require('graphql-redis-subscriptions'); const {SubscriptionManager} = require('graphql-subscriptions'); const {SubscriptionServer} = require('subscriptions-transport-ws'); +const _ = require('lodash'); const pubsub = new RedisPubSub(connectionOptions); +let setupFunctions = { + commentAdded: (options, args) => ({ + commentAdded: { + filter: (comment) => comment.asset_id === args.asset_id + }, + }), +}; + +/** + * Plugin support requires that we merge in existing setupFunctions with our new + * plugin based ones. This allows plugins to extend existing setupFunctions as well + * as provide new ones. + */ +setupFunctions = plugins.get('server', 'setupFunctions').reduce((acc, {setupFunctions}) => { + return _.merge(acc, setupFunctions); +}, setupFunctions); + module.exports = { pubsub, createGraphOptions: (req) => ({ @@ -23,13 +42,7 @@ module.exports = { subscriptionManager: new SubscriptionManager({ schema, pubsub, - setupFunctions: { - commentAdded: (options, args) => ({ - commentAdded: { - filter: (comment) => comment.asset_id === args.asset_id - }, - }), - } + setupFunctions, }), onSubscribe: (parsedMessage, baseParams, connection) => { From 6735296c3c4b9a334eb79fc051702452aa414de7 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 5 Apr 2017 17:07:14 -0400 Subject: [PATCH 10/19] Abstract setupFunctions --- graph/index.js | 20 +------------------- graph/setupFunctions.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 graph/setupFunctions.js diff --git a/graph/index.js b/graph/index.js index 021770d36..3413f2d78 100644 --- a/graph/index.js +++ b/graph/index.js @@ -1,32 +1,14 @@ const schema = require('./schema'); const Context = require('./context'); -const plugins = require('../services/plugins'); +const setupFunctions = require('./setupFunctions'); const {connectionOptions} = require('../services/redis'); const {RedisPubSub} = require('graphql-redis-subscriptions'); const {SubscriptionManager} = require('graphql-subscriptions'); const {SubscriptionServer} = require('subscriptions-transport-ws'); -const _ = require('lodash'); const pubsub = new RedisPubSub(connectionOptions); -let setupFunctions = { - commentAdded: (options, args) => ({ - commentAdded: { - filter: (comment) => comment.asset_id === args.asset_id - }, - }), -}; - -/** - * Plugin support requires that we merge in existing setupFunctions with our new - * plugin based ones. This allows plugins to extend existing setupFunctions as well - * as provide new ones. - */ -setupFunctions = plugins.get('server', 'setupFunctions').reduce((acc, {setupFunctions}) => { - return _.merge(acc, setupFunctions); -}, setupFunctions); - module.exports = { pubsub, createGraphOptions: (req) => ({ diff --git a/graph/setupFunctions.js b/graph/setupFunctions.js new file mode 100644 index 000000000..ed4d6ffa9 --- /dev/null +++ b/graph/setupFunctions.js @@ -0,0 +1,21 @@ +const plugins = require('../services/plugins'); +const _ = require('lodash'); + +// Core setup functions +let setupFunctions = { + commentAdded: (options, args) => ({ + commentAdded: { + filter: (comment) => comment.asset_id === args.asset_id + }, + }), +}; + +/** + * Plugin support requires that we merge in existing setupFunctions with our new + * plugin based ones. This allows plugins to extend existing setupFunctions as well + * as provide new ones. + */ +module.exports = plugins.get('server', 'setupFunctions').reduce((acc, {setupFunctions}) => { + + return _.merge(acc, setupFunctions); +}, setupFunctions); From b335cb349371b41fb0aca257a2f3ec7c6af8cbfa Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 5 Apr 2017 17:26:12 -0400 Subject: [PATCH 11/19] Document subscription plugins --- PLUGINS.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/PLUGINS.md b/PLUGINS.md index e5cf8d760..f04695f66 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -92,6 +92,10 @@ type RootMutation { type RootQuery { people: [Person!] } + +type Subscription { + leader: Person +} ``` Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of @@ -206,6 +210,23 @@ If your post function accepts four parameters, then it can modify the field result. It is *required* that the function resolves a promise (or returns) with the modified value or simply the original if you didn't modify it. +#### Field: `setupFunctions` + +```js +setupFunctions: { + leader: (options, args) => ({ + leader: { + filter: (person) => person.place === 1 + }, + }), +} +``` + +Setup functions allow you to create filters that control which pubsub.publish() events +send data to the client. If the type in question contains args, clients may subscribe using those arguments to further filter their subscription. + +For more information, see the [Apollo Docs](https://github.com/apollographql/graphql-subscriptions). + #### Field: `router` ```js @@ -322,6 +343,10 @@ module.exports = { type RootQuery { people: [Person!] } + + type Subscription { + leader: Person + } `, context: { Slack: () => ({ @@ -377,6 +402,13 @@ module.exports = { } } } + }, + setupFunctions: { + leader: (options, args) => ({ + leader: { + filter: (person) => person.place === 1 + } + } } }; From ffb0ee20e8ee4b61ede80810193c1debe2ec590c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 5 Apr 2017 16:15:22 -0600 Subject: [PATCH 12/19] Cleanup of subscriptions --- bin/cli-serve | 23 +++++++++------ graph/index.js | 35 ++--------------------- graph/pubsub.js | 5 ++++ graph/setupFunctions.js | 21 -------------- graph/subscriptions.js | 60 +++++++++++++++++++++++++++++++++++++++ package.json | 4 +-- services/subscriptions.js | 54 +++++++++++++++++------------------ 7 files changed, 110 insertions(+), 92 deletions(-) create mode 100644 graph/pubsub.js delete mode 100644 graph/setupFunctions.js create mode 100644 graph/subscriptions.js diff --git a/bin/cli-serve b/bin/cli-serve index eef6526bd..eca23303b 100755 --- a/bin/cli-serve +++ b/bin/cli-serve @@ -8,7 +8,7 @@ const mailer = require('../services/mailer'); const kue = require('../services/kue'); const mongoose = require('../services/mongoose'); const util = require('./util'); -const subscriptions = require('../services/subscriptions'); +const {createSubscriptionManager} = require('../graph/subscriptions'); /** * Get port from environment and store in Express. @@ -77,25 +77,29 @@ function normalizePort(val) { function onListening() { let addr = server.address(); let bind = typeof addr === 'string' - ? `pipe ${ addr}` - : `port ${ addr.port}`; - console.log(`Listening on ${ bind}`); + ? `pipe ${addr}` + : `port ${addr.port}`; + console.log(`API Server Listening on ${bind}`); } /** * Start the app. */ -function startApp() { +function startApp(program) { /** * Listen on provided port, on all network interfaces. */ server.listen(port, () => { - // Mount the subscriptions server on the application server. - subscriptions.mount(server); - }); + // Mount the websocket server if requested. + if (program.websockets) { + console.log(`Websocket Server Listening on ${port}`); + // Mount the subscriptions server on the application server. + createSubscriptionManager(server); + } + }); server.on('error', onError); server.on('listening', onListening); } @@ -106,10 +110,11 @@ function startApp() { program .option('-j, --jobs', 'enable job processing on this thread') + .option('-w, --websockets', 'enable the websocket (subscriptions) handler on this thread') .parse(process.argv); // Start the application serving. -startApp(); +startApp(program); // Enable job processing on the thread if enabled. if (program.jobs) { diff --git a/graph/index.js b/graph/index.js index 3413f2d78..4e99714d9 100644 --- a/graph/index.js +++ b/graph/index.js @@ -1,16 +1,9 @@ const schema = require('./schema'); const Context = require('./context'); -const setupFunctions = require('./setupFunctions'); - -const {connectionOptions} = require('../services/redis'); -const {RedisPubSub} = require('graphql-redis-subscriptions'); -const {SubscriptionManager} = require('graphql-subscriptions'); -const {SubscriptionServer} = require('subscriptions-transport-ws'); - -const pubsub = new RedisPubSub(connectionOptions); +const pubsub = require('./pubsub'); +const {createSubscriptionManager} = require('./subscriptions'); module.exports = { - pubsub, createGraphOptions: (req) => ({ // Schema is created already, so just include it. @@ -20,27 +13,5 @@ module.exports = { // the lifespan of this request. context: new Context(req, pubsub) }), - createSubscriptionManager: (server, path, sessionFactory) => new SubscriptionServer({ - subscriptionManager: new SubscriptionManager({ - schema, - pubsub, - setupFunctions, - }), - 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 - }) + createSubscriptionManager }; diff --git a/graph/pubsub.js b/graph/pubsub.js new file mode 100644 index 000000000..a5ee95b80 --- /dev/null +++ b/graph/pubsub.js @@ -0,0 +1,5 @@ +const {RedisPubSub} = require('graphql-redis-subscriptions'); + +const {connectionOptions} = require('../services/redis'); + +module.exports = new RedisPubSub(connectionOptions); diff --git a/graph/setupFunctions.js b/graph/setupFunctions.js deleted file mode 100644 index ed4d6ffa9..000000000 --- a/graph/setupFunctions.js +++ /dev/null @@ -1,21 +0,0 @@ -const plugins = require('../services/plugins'); -const _ = require('lodash'); - -// Core setup functions -let setupFunctions = { - commentAdded: (options, args) => ({ - commentAdded: { - filter: (comment) => comment.asset_id === args.asset_id - }, - }), -}; - -/** - * Plugin support requires that we merge in existing setupFunctions with our new - * plugin based ones. This allows plugins to extend existing setupFunctions as well - * as provide new ones. - */ -module.exports = plugins.get('server', 'setupFunctions').reduce((acc, {setupFunctions}) => { - - return _.merge(acc, setupFunctions); -}, setupFunctions); diff --git a/graph/subscriptions.js b/graph/subscriptions.js new file mode 100644 index 000000000..369b5c058 --- /dev/null +++ b/graph/subscriptions.js @@ -0,0 +1,60 @@ +const {SubscriptionManager} = require('graphql-subscriptions'); +const {SubscriptionServer} = require('subscriptions-transport-ws'); +const _ = require('lodash'); + +const pubsub = require('./pubsub'); +const schema = require('./schema'); +const Context = require('./context'); +const plugins = require('../services/plugins'); + +const {deserializeUser} = require('../services/subscriptions'); + +// Core setup functions +let setupFunctions = { + commentAdded: (options, args) => ({ + commentAdded: { + filter: (comment) => comment.asset_id === args.asset_id + }, + }), +}; + +/** + * Plugin support requires that we merge in existing setupFunctions with our new + * plugin based ones. This allows plugins to extend existing setupFunctions as well + * as provide new ones. + */ +setupFunctions = plugins.get('server', 'setupFunctions').reduce((acc, {setupFunctions}) => { + + return _.merge(acc, setupFunctions); +}, setupFunctions); + +/** + * This creates a new subscription manager. + */ +const createSubscriptionManager = (server) => new SubscriptionServer({ + subscriptionManager: new SubscriptionManager({ + schema, + pubsub, + setupFunctions, + }), + onSubscribe: (parsedMessage, baseParams, connection) => { + + // Attach the context per request. + baseParams.context = () => deserializeUser(connection.upgradeReq) + .then((req) => new Context(req, pubsub)) + .catch((err) => { + console.error(err); + + return new Context({}, pubsub); + }); + + return baseParams; + } +}, { + server, + path: '/api/v1/live' +}); + +module.exports = { + createSubscriptionManager +}; diff --git a/package.json b/package.json index 75e729350..723c6dece 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "app.js", "scripts": { "postinstall": "./bin/cli plugins reconcile --skip-remote", - "start": "./bin/cli serve --jobs", - "dev-start": "nodemon --config .nodemon.json --exec \"./bin/cli -c .env serve --jobs\"", + "start": "./bin/cli serve -j -w", + "dev-start": "nodemon -w . -w bin/cli -w bin/cli-serve --config .nodemon.json --exec \"./bin/cli -c .env serve -j -w\"", "build": "NODE_ENV=production webpack --progress -p --config webpack.config.js --bail", "build-watch": "NODE_ENV=development webpack --progress --config webpack.config.js --watch", "lint": "eslint bin/* .", diff --git a/services/subscriptions.js b/services/subscriptions.js index eaf2f9b23..8c34507f2 100644 --- a/services/subscriptions.js +++ b/services/subscriptions.js @@ -1,38 +1,36 @@ -const {createSubscriptionManager} = require('../graph'); const session = require('./session'); const passport = require('./passport'); -module.exports = class Subscriptions { +// Session data does not automatically attach to websocket req objects. +// This middleware code looks for a user in the session and, if it exists, +// attaches it to the graph req. +const deserializeUser = (req) => { + return new Promise((resolve, reject) => { + session(req, {}, () => { - // Session data does not automatically attach to websocket req objects. - // This middleware code looks for a user in the session and, if it exists, - // attaches it to the graph req. - 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); + } - 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; - req.user = user; + return resolve(req); + }); + } - return resolve(req); - }); - } else { - resolve(req); - } + // Remove the user from the request (if there was one) + if (req.user) { + delete req.user; + } - }); + // Resolve with the request (user removed possibly). + return 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); - } + }); +}; + +module.exports = { + deserializeUser }; From 97e3d7220e2098f86901a571b4629af8e4e517ad Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 10 Apr 2017 14:39:50 -0600 Subject: [PATCH 13/19] Removed test code --- client/coral-embed-live-stream/src/Embed.js | 115 ------------------ client/coral-embed-live-stream/src/index.js | 16 --- .../coral-embed-live-stream/style/default.css | 0 client/coral-framework/services/client.js | 2 + webpack.config.js | 3 +- 5 files changed, 3 insertions(+), 133 deletions(-) delete mode 100644 client/coral-embed-live-stream/src/Embed.js delete mode 100644 client/coral-embed-live-stream/src/index.js delete mode 100644 client/coral-embed-live-stream/style/default.css diff --git a/client/coral-embed-live-stream/src/Embed.js b/client/coral-embed-live-stream/src/Embed.js deleted file mode 100644 index 6be149007..000000000 --- a/client/coral-embed-live-stream/src/Embed.js +++ /dev/null @@ -1,115 +0,0 @@ -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 = { - assetID: React.PropTypes.string.isRequired, - subscribeToNewComments: React.PropTypes.func.isRequired, - data: React.PropTypes.shape({ - loading: React.PropTypes.bool.isRequired, - asset: React.PropTypes.object, - }).isRequired - } - - componentWillMount() { - const {assetID} = this.props; - this.props.subscribeToNewComments({assetID}); - } - - render() { - const {asset, loading} = this.props.data; - return ( -
-

Wyatt's Awesome Live Stream

-
- {!loading && asset && asset.comments && asset.comments.map((comment) => ( -
- - {comment.user.username} - {comment.body} - - - - -
- ))} -
-
- ); - } -} - -const COMMENT_QUERY = gql` - query Comment($assetID: ID!) { - asset(id: $assetID) { - comments(limit: 50) { - id - body - user { - username - } - created_at - } - } - } -`; - -const COMMENTS_SUBSCRIPTION = gql` - subscription onCommentAdded($assetID: ID!){ - commentAdded(asset_id: $assetID) { - id - body - user { - username - } - created_at - } - } -`; - -const withData = graphql(COMMENT_QUERY, { - options: ({assetID}) => ({ - variables: { - assetID - }, - }), - props: (props) => ({ - ...props, - subscribeToNewComments: ({assetID}) => { - return props.data.subscribeToMore({ - document: COMMENTS_SUBSCRIPTION, - variables: {assetID}, - updateQuery: (before, {subscriptionData}) => { - if (!subscriptionData.data) { - return before; - } - - const newComment = subscriptionData.data.commentAdded; - - if (!newComment) { - console.warn('last comment was null :/'); - return before; - } - - let after = { - ...before, - asset: { - ...before.asset, - comments: [newComment, ...before.asset.comments] - } - }; - - return after; - } - }); - } - }) -}); - -export default withData(Embed); diff --git a/client/coral-embed-live-stream/src/index.js b/client/coral-embed-live-stream/src/index.js deleted file mode 100644 index 564ea0ed6..000000000 --- a/client/coral-embed-live-stream/src/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import {render} from 'react-dom'; -import {ApolloProvider} from 'react-apollo'; - -import {client} from 'coral-framework/services/client'; - -// import store from 'coral-framework/services/store'; - -import Embed from './Embed'; - -render( - - - - , document.querySelector('#coralStream') -); diff --git a/client/coral-embed-live-stream/style/default.css b/client/coral-embed-live-stream/style/default.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js index 3ad43fee4..4673171b9 100644 --- a/client/coral-framework/services/client.js +++ b/client/coral-framework/services/client.js @@ -2,6 +2,8 @@ import ApolloClient, {addTypename} from 'apollo-client'; import getNetworkInterface from './transport'; import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws'; + +// TODO: replace absolute reference with something loaded from the store/page. const wsClient = new SubscriptionClient('ws://localhost:3000/api/v1/live', { reconnect: true }); diff --git a/webpack.config.js b/webpack.config.js index c951c996d..3a54e85c0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,8 +33,7 @@ const buildTargets = [ ]; const buildEmbeds = [ - 'stream', - 'live-stream' + 'stream' ]; module.exports = { From 45094fd1769747a99e3466a48f9470ed38215fb8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 18 Apr 2017 15:48:33 -0600 Subject: [PATCH 14/19] Optimized sub model --- client/coral-framework/services/client.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js index 4673171b9..f63054413 100644 --- a/client/coral-framework/services/client.js +++ b/client/coral-framework/services/client.js @@ -4,12 +4,11 @@ import getNetworkInterface from './transport'; import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws'; // TODO: replace absolute reference with something loaded from the store/page. -const wsClient = new SubscriptionClient('ws://localhost:3000/api/v1/live', { - reconnect: true -}); -const networkInterface = getNetworkInterface(); -const networkInterfaceWithSubscriptions = addGraphQLSubscriptions( - networkInterface, +// const wsClient = new SubscriptionClient('ws://localhost:3000/api/v1/live', { +// reconnect: true +// }); +const networkInterface = addGraphQLSubscriptions( + getNetworkInterface(), wsClient, ); @@ -22,7 +21,7 @@ export const client = new ApolloClient({ } return null; }, - networkInterface: networkInterfaceWithSubscriptions, + networkInterface }); export default client; From ecbb66e270b8846e67249029ddedfd6b51ff5592 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 18 Apr 2017 15:53:15 -0600 Subject: [PATCH 15/19] Fix linting --- client/coral-framework/services/client.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js index f63054413..07bd13ca1 100644 --- a/client/coral-framework/services/client.js +++ b/client/coral-framework/services/client.js @@ -1,16 +1,17 @@ import ApolloClient, {addTypename} from 'apollo-client'; import getNetworkInterface from './transport'; -import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws'; +// import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws'; // TODO: replace absolute reference with something loaded from the store/page. // const wsClient = new SubscriptionClient('ws://localhost:3000/api/v1/live', { // reconnect: true // }); -const networkInterface = addGraphQLSubscriptions( - getNetworkInterface(), - wsClient, -); +// const networkInterface = addGraphQLSubscriptions( +// getNetworkInterface(), +// wsClient, +// ); +const networkInterface = getNetworkInterface(); export const client = new ApolloClient({ connectToDevTools: true, From b559b8119847a575e07f5b491ad61bd9dab573a9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 20 Apr 2017 15:19:46 -0600 Subject: [PATCH 16/19] Removed some demo code --- routes/embed/index.js | 2 -- views/embed/live-stream.ejs | 10 ---------- 2 files changed, 12 deletions(-) delete mode 100644 views/embed/live-stream.ejs diff --git a/routes/embed/index.js b/routes/embed/index.js index 93731bee5..efa563422 100644 --- a/routes/embed/index.js +++ b/routes/embed/index.js @@ -13,8 +13,6 @@ router.use('/:embed', (req, res, next) => { return res.render('embed/stream', {customCssUrl, data}); }); - case 'live-stream': - return res.render('embed/live-stream'); default: // will return a 404. diff --git a/views/embed/live-stream.ejs b/views/embed/live-stream.ejs deleted file mode 100644 index e10f408cb..000000000 --- a/views/embed/live-stream.ejs +++ /dev/null @@ -1,10 +0,0 @@ - - - - Live Stream - - -
- - - From 441ea12a6579f0c3c0ce516d219f848705c11c39 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 21 Apr 2017 15:22:52 -0600 Subject: [PATCH 17/19] Added user metadata --- models/user.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/models/user.js b/models/user.js index 2663410a7..dd30e8033 100644 --- a/models/user.js +++ b/models/user.js @@ -123,7 +123,13 @@ const UserSchema = new mongoose.Schema({ // user id of another user type: String, - }] + }], + + // Additional metadata stored on the field. + metadata: { + default: {}, + type: Object + } }, { // This will ensure that we have proper timestamps available on this model. From 33d71df049843f38780cfee9bc9ae77657a63c38 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 25 Apr 2017 15:52:52 -0500 Subject: [PATCH 18/19] Recover the scroll. --- .../src/containers/ModerationQueue/components/Comment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index b18f239e4..203f2fbe6 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -25,7 +25,7 @@ const Comment = ({actions = [], comment, ...props}) => { const flagActions = comment.actions && comment.actions.filter(a => a.__typename === 'FlagAction'); return ( -
  • +
  • From aad66ec7e027d2ca199867c6c39041540dca03d0 Mon Sep 17 00:00:00 2001 From: riley Date: Tue, 25 Apr 2017 16:18:21 -0600 Subject: [PATCH 19/19] fix casing --- client/coral-plugin-commentbox/translations.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-plugin-commentbox/translations.json b/client/coral-plugin-commentbox/translations.json index 8ad8b1813..4110df75d 100644 --- a/client/coral-plugin-commentbox/translations.json +++ b/client/coral-plugin-commentbox/translations.json @@ -3,7 +3,7 @@ "post": "Post", "cancel": "Cancel", "reply": "Reply", - "comment": "Post a Comment", + "comment": "Post a comment", "name": "Name", "comment-post-notif": "Your comment has been posted.", "comment-post-notif-premod": "Thank you for posting. Our moderation team will review your comment shortly.", @@ -14,7 +14,7 @@ "post": "Publicar", "cancel": "Cancelar", "reply": "Responder", - "comment": "Publicar un Comentario", + "comment": "Publicar un comentario", "name": "Nombre", "comment-post-notif": "Tu comentario ha sido publicado.", "comment-post-notif-premod": "Gracias por el comentario. Nuestro equipo de moderación va a revisarlo muy pronto.",