From 109efcaa27940deaa66e5145c8567d15cd6b3dd1 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 22 Jul 2020 22:44:24 -0700 Subject: [PATCH] chore: Remove WEBSOCKETS_ENABLED flag (#1383) * chore: Remove WEBSOCKETS_ENALBED flag * lint --- .env.sample | 1 - app.json | 5 - app/components/SocketProvider.js | 3 - index.js | 6 - server/env.js | 1 - server/index.js | 278 +++++++++++++++---------------- server/services/websockets.js | 4 +- 7 files changed, 141 insertions(+), 157 deletions(-) diff --git a/.env.sample b/.env.sample index 379c02fd..b1ee6045 100644 --- a/.env.sample +++ b/.env.sample @@ -18,7 +18,6 @@ PORT=3000 FORCE_HTTPS=true ENABLE_UPDATES=true -WEBSOCKETS_ENABLED=true DEBUG=cache,presenters,events # Third party signin credentials (at least one is required) diff --git a/app.json b/app.json index 5670a107..09221239 100644 --- a/app.json +++ b/app.json @@ -39,11 +39,6 @@ "value": "true", "required": true }, - "WEBSOCKETS_ENABLED": { - "value": "true", - "required": true, - "description": "Allow realtime data to be pushed to clients over websockets" - }, "URL": { "description": "https://{your app name}.herokuapp.com", "required": true diff --git a/app/components/SocketProvider.js b/app/components/SocketProvider.js index ceb19999..f190eb7b 100644 --- a/app/components/SocketProvider.js +++ b/app/components/SocketProvider.js @@ -13,7 +13,6 @@ import PoliciesStore from "stores/PoliciesStore"; import ViewsStore from "stores/ViewsStore"; import AuthStore from "stores/AuthStore"; import UiStore from "stores/UiStore"; -import env from "env"; export const SocketContext: any = React.createContext(); @@ -35,8 +34,6 @@ class SocketProvider extends React.Component { @observable socket; componentDidMount() { - if (!env.WEBSOCKETS_ENABLED) return; - this.socket = io(window.location.origin, { path: "/realtime", }); diff --git a/index.js b/index.js index 00d10400..5f8f76aa 100644 --- a/index.js +++ b/index.js @@ -62,12 +62,6 @@ if (!process.env.REDIS_URL) { process.exit(1); } -if (!process.env.WEBSOCKETS_ENABLED) { - console.log( - "WARNING: Websockets are disabled. Set WEBSOCKETS_ENABLED env variable to true to enable" - ); -} - if (process.env.NODE_ENV === "production") { console.log("\n\x1b[33m%s\x1b[0m", "Running Outline in production mode."); } else if (process.env.NODE_ENV === "development") { diff --git a/server/env.js b/server/env.js index cb46529b..1f74cd8d 100644 --- a/server/env.js +++ b/server/env.js @@ -7,6 +7,5 @@ export default { SLACK_KEY: process.env.SLACK_KEY, SLACK_APP_ID: process.env.SLACK_APP_ID, SUBDOMAINS_ENABLED: process.env.SUBDOMAINS_ENABLED === "true", - WEBSOCKETS_ENABLED: process.env.WEBSOCKETS_ENABLED === "true", GOOGLE_ANALYTICS_ID: process.env.GOOGLE_ANALYTICS_ID, }; diff --git a/server/index.js b/server/index.js index 1ec39eda..22247b80 100644 --- a/server/index.js +++ b/server/index.js @@ -12,168 +12,166 @@ import policy from "./policies"; const server = http.createServer(app.callback()); let io; -if (process.env.WEBSOCKETS_ENABLED === "true") { - const { can } = policy; +const { can } = policy; - io = IO(server, { - path: "/realtime", - serveClient: false, - cookie: false, - }); +io = IO(server, { + path: "/realtime", + serveClient: false, + cookie: false, +}); - io.adapter( - socketRedisAdapter({ - pubClient: client, - subClient: subscriber, - }) - ); +io.adapter( + socketRedisAdapter({ + pubClient: client, + subClient: subscriber, + }) +); - SocketAuth(io, { - authenticate: async (socket, data, callback) => { - const { token } = data; +SocketAuth(io, { + authenticate: async (socket, data, callback) => { + const { token } = data; - try { - const user = await getUserForJWT(token); - socket.client.user = user; + try { + const user = await getUserForJWT(token); + socket.client.user = user; - // store the mapping between socket id and user id in redis - // so that it is accessible across multiple server nodes - await client.hset(socket.id, "userId", user.id); + // store the mapping between socket id and user id in redis + // so that it is accessible across multiple server nodes + await client.hset(socket.id, "userId", user.id); - return callback(null, true); - } catch (err) { - return callback(err); + return callback(null, true); + } catch (err) { + return callback(err); + } + }, + postAuthenticate: async (socket, data) => { + const { user } = socket.client; + + // the rooms associated with the current team + // and user so we can send authenticated events + let rooms = [`team-${user.teamId}`, `user-${user.id}`]; + + // the rooms associated with collections this user + // has access to on connection. New collection subscriptions + // are managed from the client as needed through the 'join' event + const collectionIds = await user.collectionIds(); + collectionIds.forEach(collectionId => + rooms.push(`collection-${collectionId}`) + ); + + // join all of the rooms at once + socket.join(rooms); + + // allow the client to request to join rooms + socket.on("join", async event => { + // user is joining a collection channel, because their permissions have + // changed, granting them access. + if (event.collectionId) { + const collection = await Collection.scope({ + method: ["withMembership", user.id], + }).findByPk(event.collectionId); + + if (can(user, "read", collection)) { + socket.join(`collection-${event.collectionId}`); + } } - }, - postAuthenticate: async (socket, data) => { - const { user } = socket.client; - // the rooms associated with the current team - // and user so we can send authenticated events - let rooms = [`team-${user.teamId}`, `user-${user.id}`]; + // user is joining a document channel, because they have navigated to + // view a document. + if (event.documentId) { + const document = await Document.findByPk(event.documentId, { + userId: user.id, + }); - // the rooms associated with collections this user - // has access to on connection. New collection subscriptions - // are managed from the client as needed through the 'join' event - const collectionIds = await user.collectionIds(); - collectionIds.forEach(collectionId => - rooms.push(`collection-${collectionId}`) - ); - - // join all of the rooms at once - socket.join(rooms); - - // allow the client to request to join rooms - socket.on("join", async event => { - // user is joining a collection channel, because their permissions have - // changed, granting them access. - if (event.collectionId) { - const collection = await Collection.scope({ - method: ["withMembership", user.id], - }).findByPk(event.collectionId); - - if (can(user, "read", collection)) { - socket.join(`collection-${event.collectionId}`); - } - } - - // user is joining a document channel, because they have navigated to - // view a document. - if (event.documentId) { - const document = await Document.findByPk(event.documentId, { - userId: user.id, - }); - - if (can(user, "read", document)) { - const room = `document-${event.documentId}`; - - await View.touch(event.documentId, user.id, event.isEditing); - const editing = await View.findRecentlyEditingByDocument( - event.documentId - ); - - socket.join(room, () => { - // let everyone else in the room know that a new user joined - io.to(room).emit("user.join", { - userId: user.id, - documentId: event.documentId, - isEditing: event.isEditing, - }); - - // let this user know who else is already present in the room - io.in(room).clients(async (err, sockets) => { - if (err) throw err; - - // because a single user can have multiple socket connections we - // need to make sure that only unique userIds are returned. A Map - // makes this easy. - let userIds = new Map(); - for (const socketId of sockets) { - const userId = await client.hget(socketId, "userId"); - userIds.set(userId, userId); - } - socket.emit("document.presence", { - documentId: event.documentId, - userIds: Array.from(userIds.keys()), - editingIds: editing.map(view => view.userId), - }); - }); - }); - } - } - }); - - // allow the client to request to leave rooms - socket.on("leave", event => { - if (event.collectionId) { - socket.leave(`collection-${event.collectionId}`); - } - if (event.documentId) { + if (can(user, "read", document)) { const room = `document-${event.documentId}`; - socket.leave(room, () => { - io.to(room).emit("user.leave", { + + await View.touch(event.documentId, user.id, event.isEditing); + const editing = await View.findRecentlyEditingByDocument( + event.documentId + ); + + socket.join(room, () => { + // let everyone else in the room know that a new user joined + io.to(room).emit("user.join", { userId: user.id, documentId: event.documentId, + isEditing: event.isEditing, + }); + + // let this user know who else is already present in the room + io.in(room).clients(async (err, sockets) => { + if (err) throw err; + + // because a single user can have multiple socket connections we + // need to make sure that only unique userIds are returned. A Map + // makes this easy. + let userIds = new Map(); + for (const socketId of sockets) { + const userId = await client.hget(socketId, "userId"); + userIds.set(userId, userId); + } + socket.emit("document.presence", { + documentId: event.documentId, + userIds: Array.from(userIds.keys()), + editingIds: editing.map(view => view.userId), + }); }); }); } - }); + } + }); - socket.on("disconnecting", () => { - const rooms = Object.keys(socket.rooms); - - rooms.forEach(room => { - if (room.startsWith("document-")) { - const documentId = room.replace("document-", ""); - io.to(room).emit("user.leave", { - userId: user.id, - documentId, - }); - } - }); - }); - - socket.on("presence", async event => { + // allow the client to request to leave rooms + socket.on("leave", event => { + if (event.collectionId) { + socket.leave(`collection-${event.collectionId}`); + } + if (event.documentId) { const room = `document-${event.documentId}`; - - if (event.documentId && socket.rooms[room]) { - const view = await View.touch( - event.documentId, - user.id, - event.isEditing - ); - view.user = user; - - io.to(room).emit("user.presence", { + socket.leave(room, () => { + io.to(room).emit("user.leave", { userId: user.id, documentId: event.documentId, - isEditing: event.isEditing, + }); + }); + } + }); + + socket.on("disconnecting", () => { + const rooms = Object.keys(socket.rooms); + + rooms.forEach(room => { + if (room.startsWith("document-")) { + const documentId = room.replace("document-", ""); + io.to(room).emit("user.leave", { + userId: user.id, + documentId, }); } }); - }, - }); -} + }); + + socket.on("presence", async event => { + const room = `document-${event.documentId}`; + + if (event.documentId && socket.rooms[room]) { + const view = await View.touch( + event.documentId, + user.id, + event.isEditing + ); + view.user = user; + + io.to(room).emit("user.presence", { + userId: user.id, + documentId: event.documentId, + isEditing: event.isEditing, + }); + } + }); + }, +}); server.on("error", err => { throw err; diff --git a/server/services/websockets.js b/server/services/websockets.js index 95adbef1..6008730a 100644 --- a/server/services/websockets.js +++ b/server/services/websockets.js @@ -13,7 +13,9 @@ import subHours from "date-fns/sub_hours"; export default class Websockets { async on(event: Event) { - if (process.env.WEBSOCKETS_ENABLED !== "true" || !socketio) return; + if (!socketio) { + return; + } switch (event.name) { case "documents.publish":