chore: Move to Typescript (#2783)
This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously. closes #1282
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// @flow
|
||||
import { onAuthenticatePayload } from "@hocuspocus/server";
|
||||
import { Document } from "@server/models";
|
||||
import { getUserForJWT } from "@server/utils/jwt";
|
||||
import { AuthenticationError } from "../errors";
|
||||
import { Document } from "../models";
|
||||
import policy from "../policies";
|
||||
import { getUserForJWT } from "../utils/jwt";
|
||||
|
||||
const { can } = policy;
|
||||
|
||||
@@ -11,26 +11,26 @@ export default class Authentication {
|
||||
connection,
|
||||
token,
|
||||
documentName,
|
||||
}: {
|
||||
connection: { readOnly: boolean },
|
||||
token: string,
|
||||
documentName: string,
|
||||
}) {
|
||||
}: onAuthenticatePayload) {
|
||||
// allows for different entity types to use this multiplayer provider later
|
||||
const [, documentId] = documentName.split(".");
|
||||
|
||||
if (!token) {
|
||||
throw new AuthenticationError("Authentication required");
|
||||
throw AuthenticationError("Authentication required");
|
||||
}
|
||||
|
||||
const user = await getUserForJWT(token);
|
||||
|
||||
if (user.isSuspended) {
|
||||
throw new AuthenticationError("Account suspended");
|
||||
throw AuthenticationError("Account suspended");
|
||||
}
|
||||
|
||||
const document = await Document.findByPk(documentId, { userId: user.id });
|
||||
const document = await Document.findByPk(documentId, {
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!can(user, "read", document)) {
|
||||
throw new AuthenticationError("Authorization required");
|
||||
throw AuthenticationError("Authorization required");
|
||||
}
|
||||
|
||||
// set document to read only for the current user, thus changes will not be
|
||||
@@ -1,22 +0,0 @@
|
||||
// @flow
|
||||
import Logger from "../logging/logger";
|
||||
import { User } from "../models";
|
||||
|
||||
export default class CollaborationLogger {
|
||||
async onLoadDocument(data: {
|
||||
documentName: string,
|
||||
context: { user: User },
|
||||
}) {
|
||||
Logger.info("hocuspocus", `Loaded document "${data.documentName}"`, {
|
||||
userId: data.context.user.id,
|
||||
});
|
||||
}
|
||||
|
||||
async onConnect(data: { documentName: string, context: { user: User } }) {
|
||||
Logger.info("hocuspocus", `New connection to "${data.documentName}"`);
|
||||
}
|
||||
|
||||
async onDisconnect(data: { documentName: string, context: { user: User } }) {
|
||||
Logger.info("hocuspocus", `Connection to "${data.documentName}" closed `);
|
||||
}
|
||||
}
|
||||
22
server/collaboration/logger.ts
Normal file
22
server/collaboration/logger.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
onConnectPayload,
|
||||
onDisconnectPayload,
|
||||
onLoadDocumentPayload,
|
||||
} from "@hocuspocus/server";
|
||||
import Logger from "@server/logging/logger";
|
||||
|
||||
export default class CollaborationLogger {
|
||||
async onLoadDocument(data: onLoadDocumentPayload) {
|
||||
Logger.info("hocuspocus", `Loaded document "${data.documentName}"`, {
|
||||
userId: data.context.user.id,
|
||||
});
|
||||
}
|
||||
|
||||
async onConnect(data: onConnectPayload) {
|
||||
Logger.info("hocuspocus", `New connection to "${data.documentName}"`);
|
||||
}
|
||||
|
||||
async onDisconnect(data: onDisconnectPayload) {
|
||||
Logger.info("hocuspocus", `Connection to "${data.documentName}" closed `);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,15 @@
|
||||
// @flow
|
||||
import { onChangePayload, onLoadDocumentPayload } from "@hocuspocus/server";
|
||||
import { debounce } from "lodash";
|
||||
import * as Y from "yjs";
|
||||
import Logger from "@server/logging/logger";
|
||||
import { Document, User } from "@server/models";
|
||||
import documentUpdater from "../commands/documentUpdater";
|
||||
import Logger from "../logging/logger";
|
||||
import { Document, User } from "../models";
|
||||
import markdownToYDoc from "./utils/markdownToYDoc";
|
||||
|
||||
const DELAY = 3000;
|
||||
|
||||
export default class Persistence {
|
||||
async onLoadDocument({
|
||||
documentName,
|
||||
...data
|
||||
}: {
|
||||
documentName: string,
|
||||
document: Y.Doc,
|
||||
}) {
|
||||
async onLoadDocument({ documentName, ...data }: onLoadDocumentPayload) {
|
||||
const [, documentId] = documentName.split(".");
|
||||
const fieldName = "default";
|
||||
|
||||
@@ -40,23 +34,20 @@ export default class Persistence {
|
||||
);
|
||||
const ydoc = markdownToYDoc(document.text, fieldName);
|
||||
const state = Y.encodeStateAsUpdate(ydoc);
|
||||
|
||||
await document.update({ state: Buffer.from(state) }, { hooks: false });
|
||||
await document.update(
|
||||
{
|
||||
state: Buffer.from(state),
|
||||
},
|
||||
{
|
||||
hooks: false,
|
||||
}
|
||||
);
|
||||
return ydoc;
|
||||
}
|
||||
|
||||
onChange = debounce(
|
||||
async ({
|
||||
document,
|
||||
context,
|
||||
documentName,
|
||||
}: {
|
||||
document: Y.Doc,
|
||||
context: { user: ?User },
|
||||
documentName: string,
|
||||
}) => {
|
||||
async ({ document, context, documentName }: onChangePayload) => {
|
||||
const [, documentId] = documentName.split(".");
|
||||
|
||||
Logger.info("database", `Persisting ${documentId}`);
|
||||
|
||||
try {
|
||||
@@ -1,66 +0,0 @@
|
||||
// @flow
|
||||
import Metrics from "../logging/metrics";
|
||||
|
||||
export default class Tracing {
|
||||
onLoadDocument({
|
||||
documentName,
|
||||
instance,
|
||||
}: {
|
||||
documentName: string,
|
||||
instance: any,
|
||||
}) {
|
||||
Metrics.increment("collaboration.load_document", { documentName });
|
||||
|
||||
Metrics.gaugePerInstance(
|
||||
"collaboration.documents_count",
|
||||
instance.getDocumentsCount()
|
||||
);
|
||||
}
|
||||
|
||||
onAuthenticationFailed({ documentName }: { documentName: string }) {
|
||||
Metrics.increment("collaboration.authentication_failed", { documentName });
|
||||
}
|
||||
|
||||
onConnect({
|
||||
documentName,
|
||||
instance,
|
||||
}: {
|
||||
documentName: string,
|
||||
instance: any,
|
||||
}) {
|
||||
Metrics.increment("collaboration.connect", { documentName });
|
||||
Metrics.gaugePerInstance(
|
||||
"collaboration.connections_count",
|
||||
instance.getConnectionsCount()
|
||||
);
|
||||
}
|
||||
|
||||
onDisconnect({
|
||||
documentName,
|
||||
instance,
|
||||
}: {
|
||||
documentName: string,
|
||||
instance: any,
|
||||
}) {
|
||||
Metrics.increment("collaboration.disconnect", { documentName });
|
||||
Metrics.gaugePerInstance(
|
||||
"collaboration.connections_count",
|
||||
instance.getConnectionsCount()
|
||||
);
|
||||
|
||||
Metrics.gaugePerInstance(
|
||||
"collaboration.documents_count",
|
||||
// -1 adjustment because hook is called before document is removed
|
||||
instance.getDocumentsCount() - 1
|
||||
);
|
||||
}
|
||||
|
||||
onChange({ documentName }: { documentName: string }) {
|
||||
Metrics.increment("collaboration.change", { documentName });
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
Metrics.gaugePerInstance("collaboration.connections_count", 0);
|
||||
Metrics.gaugePerInstance("collaboration.documents_count", 0);
|
||||
}
|
||||
}
|
||||
60
server/collaboration/tracing.ts
Normal file
60
server/collaboration/tracing.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
onChangePayload,
|
||||
onConnectPayload,
|
||||
onDisconnectPayload,
|
||||
onLoadDocumentPayload,
|
||||
} from "@hocuspocus/server";
|
||||
import Metrics from "@server/logging/metrics";
|
||||
|
||||
export default class Tracing {
|
||||
onLoadDocument({ documentName, instance }: onLoadDocumentPayload) {
|
||||
Metrics.increment("collaboration.load_document", {
|
||||
documentName,
|
||||
});
|
||||
Metrics.gaugePerInstance(
|
||||
"collaboration.documents_count",
|
||||
instance.getDocumentsCount()
|
||||
);
|
||||
}
|
||||
|
||||
onAuthenticationFailed({ documentName }: { documentName: string }) {
|
||||
Metrics.increment("collaboration.authentication_failed", {
|
||||
documentName,
|
||||
});
|
||||
}
|
||||
|
||||
onConnect({ documentName, instance }: onConnectPayload) {
|
||||
Metrics.increment("collaboration.connect", {
|
||||
documentName,
|
||||
});
|
||||
Metrics.gaugePerInstance(
|
||||
"collaboration.connections_count",
|
||||
instance.getConnectionsCount()
|
||||
);
|
||||
}
|
||||
|
||||
onDisconnect({ documentName, instance }: onDisconnectPayload) {
|
||||
Metrics.increment("collaboration.disconnect", {
|
||||
documentName,
|
||||
});
|
||||
Metrics.gaugePerInstance(
|
||||
"collaboration.connections_count",
|
||||
instance.getConnectionsCount()
|
||||
);
|
||||
Metrics.gaugePerInstance(
|
||||
"collaboration.documents_count", // -1 adjustment because hook is called before document is removed
|
||||
instance.getDocumentsCount() - 1
|
||||
);
|
||||
}
|
||||
|
||||
onChange({ documentName }: onChangePayload) {
|
||||
Metrics.increment("collaboration.change", {
|
||||
documentName,
|
||||
});
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
Metrics.gaugePerInstance("collaboration.connections_count", 0);
|
||||
Metrics.gaugePerInstance("collaboration.documents_count", 0);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
// @flow
|
||||
import { Node, Fragment } from "prosemirror-model";
|
||||
import { parser, schema } from "rich-markdown-editor";
|
||||
import { prosemirrorToYDoc } from "y-prosemirror";
|
||||
import * as Y from "yjs";
|
||||
import embeds from "../../../shared/embeds";
|
||||
import embeds from "@shared/embeds";
|
||||
|
||||
export default function markdownToYDoc(
|
||||
markdown: string,
|
||||
fieldName?: string = "default"
|
||||
fieldName = "default"
|
||||
): Y.Doc {
|
||||
let node = parser.parse(markdown);
|
||||
|
||||
@@ -16,9 +15,11 @@ export default function markdownToYDoc(
|
||||
// on the server we need to mimic this behavior.
|
||||
function urlsToEmbeds(node: Node): Node {
|
||||
if (node.type.name === "paragraph") {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'content' does not exist on type 'Fragmen... Remove this comment to see the full error message
|
||||
for (const textNode of node.content.content) {
|
||||
for (const embed of embeds) {
|
||||
if (textNode.text && embed.matcher(textNode.text)) {
|
||||
// @ts-expect-error ts-migrate(2322) FIXME: Type 'ProsemirrorNode<Schema<never, never>> | null... Remove this comment to see the full error message
|
||||
return schema.nodes.embed.createAndFill({
|
||||
href: textNode.text,
|
||||
});
|
||||
@@ -29,6 +30,7 @@ export default function markdownToYDoc(
|
||||
|
||||
if (node.content) {
|
||||
const contentAsArray =
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'content' does not exist on type 'Fragmen... Remove this comment to see the full error message
|
||||
node.content instanceof Fragment ? node.content.content : node.content;
|
||||
node.content = Fragment.fromArray(contentAsArray.map(urlsToEmbeds));
|
||||
}
|
||||
@@ -37,6 +39,5 @@ export default function markdownToYDoc(
|
||||
}
|
||||
|
||||
node = urlsToEmbeds(node);
|
||||
|
||||
return prosemirrorToYDoc(node, fieldName);
|
||||
}
|
||||
Reference in New Issue
Block a user