fix: Only update views in collaborative server on data change (#5804)
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Extension,
|
Extension,
|
||||||
onAwarenessUpdatePayload,
|
|
||||||
onDisconnectPayload,
|
onDisconnectPayload,
|
||||||
|
onChangePayload,
|
||||||
} from "@hocuspocus/server";
|
} from "@hocuspocus/server";
|
||||||
import { Second } from "@shared/utils/time";
|
import { Minute } from "@shared/utils/time";
|
||||||
import Logger from "@server/logging/Logger";
|
import Logger from "@server/logging/Logger";
|
||||||
import { trace } from "@server/logging/tracing";
|
import { trace } from "@server/logging/tracing";
|
||||||
import { View } from "@server/models";
|
import { View } from "@server/models";
|
||||||
@@ -11,63 +11,44 @@ import { View } from "@server/models";
|
|||||||
@trace()
|
@trace()
|
||||||
export class ViewsExtension implements Extension {
|
export class ViewsExtension implements Extension {
|
||||||
/**
|
/**
|
||||||
* Map of socketId -> intervals
|
* Map of last view recorded by socket
|
||||||
*/
|
*/
|
||||||
intervalsBySocket: Map<string, NodeJS.Timer> = new Map();
|
lastViewBySocket: Map<string, Date> = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onAwarenessUpdate hook
|
* onChange hook. When a user changes a document, we update their "viewedAt"
|
||||||
* @param data The awareness payload
|
* timestamp if it's been more than a minute since their last change.
|
||||||
|
*
|
||||||
|
* @param data The change payload
|
||||||
*/
|
*/
|
||||||
async onAwarenessUpdate({
|
async onChange({ documentName, context, socketId }: onChangePayload) {
|
||||||
documentName,
|
const lastUpdate = this.lastViewBySocket.get(socketId);
|
||||||
// @ts-expect-error Hocuspocus types are wrong
|
|
||||||
connection,
|
|
||||||
context,
|
|
||||||
socketId,
|
|
||||||
}: onAwarenessUpdatePayload) {
|
|
||||||
if (this.intervalsBySocket.get(socketId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [, documentId] = documentName.split(".");
|
const [, documentId] = documentName.split(".");
|
||||||
|
|
||||||
const updateView = async () => {
|
if (!lastUpdate || Date.now() - lastUpdate.getTime() > Minute) {
|
||||||
|
this.lastViewBySocket.set(socketId, new Date());
|
||||||
|
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
"multiplayer",
|
"multiplayer",
|
||||||
`Updating last viewed at for "${documentName}"`
|
`User ${context.user.id} viewed "${documentName}"`
|
||||||
);
|
);
|
||||||
try {
|
await Promise.all([
|
||||||
await View.touch(documentId, context.user.id, !connection.readOnly);
|
View.touch(documentId, context.user.id, true),
|
||||||
} catch (err) {
|
context.user.update({ lastViewedAt: new Date() }),
|
||||||
Logger.error(
|
]);
|
||||||
`Failed to update last viewed at for "${documentName}"`,
|
|
||||||
err,
|
|
||||||
{
|
|
||||||
documentId,
|
|
||||||
userId: context.user.id,
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set up an interval to update the last viewed at timestamp continuously
|
|
||||||
// while the user is connected. This should only be done once per socket.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
const interval = setInterval(updateView, 30 * Second);
|
|
||||||
|
|
||||||
this.intervalsBySocket.set(socketId, interval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onDisconnect hook
|
* onDisconnect hook. When a user disconnects, we remove their socket from
|
||||||
|
* the lastViewBySocket map to cleanup memory.
|
||||||
|
*
|
||||||
* @param data The disconnect payload
|
* @param data The disconnect payload
|
||||||
*/
|
*/
|
||||||
async onDisconnect({ socketId }: onDisconnectPayload) {
|
async onDisconnect({ socketId }: onDisconnectPayload) {
|
||||||
const interval = this.intervalsBySocket.get(socketId);
|
const interval = this.lastViewBySocket.get(socketId);
|
||||||
if (interval) {
|
if (interval) {
|
||||||
clearInterval(interval);
|
this.lastViewBySocket.delete(socketId);
|
||||||
this.intervalsBySocket.delete(socketId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,9 +57,6 @@ export class ViewsExtension implements Extension {
|
|||||||
* @param data The destroy payload
|
* @param data The destroy payload
|
||||||
*/
|
*/
|
||||||
async onDestroy() {
|
async onDestroy() {
|
||||||
this.intervalsBySocket.forEach((interval, socketId) => {
|
this.lastViewBySocket = new Map();
|
||||||
clearInterval(interval);
|
|
||||||
this.intervalsBySocket.delete(socketId);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user