Collaborative editing (#1660)

This commit is contained in:
Tom Moor
2021-09-10 22:46:57 -07:00
committed by GitHub
parent 0a998789a3
commit 801f6681ba
144 changed files with 3552 additions and 310 deletions

28
server/services/admin.js Normal file
View File

@@ -0,0 +1,28 @@
// @flow
import http from "http";
import { createBullBoard } from "@bull-board/api";
import { BullAdapter } from "@bull-board/api/bullAdapter";
import { KoaAdapter } from "@bull-board/koa";
import Koa from "koa";
import {
emailsQueue,
globalEventQueue,
processorEventQueue,
websocketsQueue,
} from "../queues";
export default function init(app: Koa, server?: http.Server) {
const serverAdapter = new KoaAdapter();
createBullBoard({
queues: [
new BullAdapter(globalEventQueue),
new BullAdapter(processorEventQueue),
new BullAdapter(emailsQueue),
new BullAdapter(websocketsQueue),
],
serverAdapter,
});
serverAdapter.setBasePath("/admin");
app.use(serverAdapter.registerPlugin());
}

View File

@@ -0,0 +1,37 @@
// @flow
import http from "http";
import { Logger } from "@hocuspocus/extension-logger";
import { Server } from "@hocuspocus/server";
import Koa from "koa";
import websocket from "koa-easy-ws";
import Router from "koa-router";
import AuthenticationExtension from "../collaboration/authentication";
import PersistenceExtension from "../collaboration/persistence";
export default function init(app: Koa, server: http.Server) {
const router = new Router();
const hocuspocus = Server.configure({
extensions: [
new AuthenticationExtension(),
new PersistenceExtension(),
new Logger(),
],
});
// Websockets for collaborative editing
router.get("/collaboration/:documentName", async (ctx) => {
let { documentName } = ctx.params;
if (ctx.ws) {
const ws = await ctx.ws();
hocuspocus.handleConnection(ws, ctx.request, documentName);
}
ctx.response.status = 101;
});
app.use(websocket());
app.use(router.routes());
app.use(router.allowedMethods());
}

View File

@@ -1,6 +1,8 @@
// @flow
import admin from "./admin";
import collaboration from "./collaboration";
import web from "./web";
import websockets from "./websockets";
import worker from "./worker";
export default { web, websockets, worker };
export default { websockets, collaboration, admin, web, worker };

View File

@@ -7,14 +7,12 @@ import {
referrerPolicy,
} from "koa-helmet";
import mount from "koa-mount";
import onerror from "koa-onerror";
import enforceHttps from "koa-sslify";
import emails from "../emails";
import env from "../env";
import routes from "../routes";
import api from "../routes/api";
import auth from "../routes/auth";
import Sentry from "../sentry";
const isProduction = env.NODE_ENV === "production";
const isTest = env.NODE_ENV === "test";
@@ -101,44 +99,6 @@ export default function init(app: Koa = new Koa(), server?: http.Server): Koa {
app.use(mount("/emails", emails));
}
// catch errors in one place, automatically set status and response headers
onerror(app);
app.on("error", (error, ctx) => {
// we don't need to report every time a request stops to the bug tracker
if (error.code === "EPIPE" || error.code === "ECONNRESET") {
console.warn("Connection error", { error });
return;
}
if (process.env.SENTRY_DSN) {
Sentry.withScope(function (scope) {
const requestId = ctx.headers["x-request-id"];
if (requestId) {
scope.setTag("request_id", requestId);
}
const authType = ctx.state ? ctx.state.authType : undefined;
if (authType) {
scope.setTag("auth_type", authType);
}
const userId =
ctx.state && ctx.state.user ? ctx.state.user.id : undefined;
if (userId) {
scope.setUser({ id: userId });
}
scope.addEventProcessor(function (event) {
return Sentry.Handlers.parseRequest(event, ctx.request);
});
Sentry.captureException(error);
});
} else {
console.error(error);
}
});
app.use(mount("/auth", auth));
app.use(mount("/api", api));

View File

@@ -10,14 +10,14 @@ import policy from "../policies";
import { websocketsQueue } from "../queues";
import WebsocketsProcessor from "../queues/processors/websockets";
import { client, subscriber } from "../redis";
import Sentry from "../sentry";
import { getUserForJWT } from "../utils/jwt";
import * as metrics from "../utils/metrics";
import Sentry from "../utils/sentry";
const { can } = policy;
const websockets = new WebsocketsProcessor();
export default function init(app: Koa, server: http.Server) {
// Websockets for events and non-collaborative documents
const io = IO(server, {
path: "/realtime",
serveClient: false,
@@ -226,6 +226,9 @@ export default function init(app: Koa, server: http.Server) {
},
});
// Handle events from event queue that should be sent to the clients down ws
const websockets = new WebsocketsProcessor();
websocketsQueue.process(async function websocketEventsProcessor(job) {
const event = job.data;
websockets.on(event, io).catch((error) => {

View File

@@ -16,7 +16,7 @@ import Imports from "../queues/processors/imports";
import Notifications from "../queues/processors/notifications";
import Revisions from "../queues/processors/revisions";
import Slack from "../queues/processors/slack";
import Sentry from "../sentry";
import Sentry from "../utils/sentry";
const log = debug("queue");