mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
This PR introduces the following changes: - New Vue stores: `collaborationStore` and `pushConnectionStore` - Front-end push connection handling overhaul: Keep only a singe connection open and handle it from the new store - Add user avatars in the editor header when there are multiple users working on the same workflow - Sending a heartbeat event to back-end service periodically to confirm user is still active - Back-end overhauls (authored by @tomi): - Implementing a cleanup procedure that removes inactive users - Refactoring collaboration service current implementation --------- Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com>
80 lines
1.9 KiB
TypeScript
80 lines
1.9 KiB
TypeScript
import type { User } from '@db/entities/User';
|
|
import type { Workflow } from 'n8n-workflow';
|
|
import { Service } from 'typedi';
|
|
|
|
type ActiveWorkflowUser = {
|
|
userId: User['id'];
|
|
lastSeen: Date;
|
|
};
|
|
|
|
type UserStateByUserId = Map<User['id'], ActiveWorkflowUser>;
|
|
|
|
type State = {
|
|
activeUsersByWorkflowId: Map<Workflow['id'], UserStateByUserId>;
|
|
};
|
|
|
|
/**
|
|
* State management for the collaboration service
|
|
*/
|
|
@Service()
|
|
export class CollaborationState {
|
|
private state: State = {
|
|
activeUsersByWorkflowId: new Map(),
|
|
};
|
|
|
|
addActiveWorkflowUser(workflowId: Workflow['id'], userId: User['id']) {
|
|
const { activeUsersByWorkflowId } = this.state;
|
|
|
|
let activeUsers = activeUsersByWorkflowId.get(workflowId);
|
|
if (!activeUsers) {
|
|
activeUsers = new Map();
|
|
activeUsersByWorkflowId.set(workflowId, activeUsers);
|
|
}
|
|
|
|
activeUsers.set(userId, {
|
|
userId,
|
|
lastSeen: new Date(),
|
|
});
|
|
}
|
|
|
|
removeActiveWorkflowUser(workflowId: Workflow['id'], userId: User['id']) {
|
|
const { activeUsersByWorkflowId } = this.state;
|
|
|
|
const activeUsers = activeUsersByWorkflowId.get(workflowId);
|
|
if (!activeUsers) {
|
|
return;
|
|
}
|
|
|
|
activeUsers.delete(userId);
|
|
if (activeUsers.size === 0) {
|
|
activeUsersByWorkflowId.delete(workflowId);
|
|
}
|
|
}
|
|
|
|
getActiveWorkflowUsers(workflowId: Workflow['id']): ActiveWorkflowUser[] {
|
|
const workflowState = this.state.activeUsersByWorkflowId.get(workflowId);
|
|
if (!workflowState) {
|
|
return [];
|
|
}
|
|
|
|
return [...workflowState.values()];
|
|
}
|
|
|
|
/**
|
|
* Removes all users that have not been seen in a given time
|
|
*/
|
|
cleanInactiveUsers(workflowId: Workflow['id'], inactivityCleanUpTimeInMs: number) {
|
|
const activeUsers = this.state.activeUsersByWorkflowId.get(workflowId);
|
|
if (!activeUsers) {
|
|
return;
|
|
}
|
|
|
|
const now = Date.now();
|
|
for (const user of activeUsers.values()) {
|
|
if (now - user.lastSeen.getTime() > inactivityCleanUpTimeInMs) {
|
|
activeUsers.delete(user.userId);
|
|
}
|
|
}
|
|
}
|
|
}
|