mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
import { defineStore } from 'pinia';
|
|
import { ref } from 'vue';
|
|
import { useRoute } from 'vue-router';
|
|
import type { Collaborator } from '@n8n/api-types';
|
|
|
|
import { STORES, PLACEHOLDER_EMPTY_WORKFLOW_ID, TIME } from '@/constants';
|
|
import { useBeforeUnload } from '@/composables/useBeforeUnload';
|
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
import { usePushConnectionStore } from '@/stores/pushConnection.store';
|
|
import { useUsersStore } from '@/stores/users.store';
|
|
import { useUIStore } from '@/stores/ui.store';
|
|
|
|
const HEARTBEAT_INTERVAL = 5 * TIME.MINUTE;
|
|
|
|
/**
|
|
* Store for tracking active users for workflows. I.e. to show
|
|
* who is collaboratively viewing/editing the workflow at the same time.
|
|
*/
|
|
export const useCollaborationStore = defineStore(STORES.COLLABORATION, () => {
|
|
const pushStore = usePushConnectionStore();
|
|
const workflowsStore = useWorkflowsStore();
|
|
const usersStore = useUsersStore();
|
|
const uiStore = useUIStore();
|
|
|
|
const route = useRoute();
|
|
const { addBeforeUnloadEventBindings, removeBeforeUnloadEventBindings, addBeforeUnloadHandler } =
|
|
useBeforeUnload({ route });
|
|
const unloadTimeout = ref<NodeJS.Timeout | null>(null);
|
|
|
|
addBeforeUnloadHandler(() => {
|
|
// Notify that workflow is closed straight away
|
|
notifyWorkflowClosed();
|
|
if (uiStore.stateIsDirty) {
|
|
// If user decided to stay on the page we notify that the workflow is opened again
|
|
unloadTimeout.value = setTimeout(() => notifyWorkflowOpened, 5 * TIME.SECOND);
|
|
}
|
|
});
|
|
|
|
const collaborators = ref<Collaborator[]>([]);
|
|
|
|
const heartbeatTimer = ref<number | null>(null);
|
|
|
|
const startHeartbeat = () => {
|
|
stopHeartbeat();
|
|
heartbeatTimer.value = window.setInterval(notifyWorkflowOpened, HEARTBEAT_INTERVAL);
|
|
};
|
|
|
|
const stopHeartbeat = () => {
|
|
if (heartbeatTimer.value !== null) {
|
|
clearInterval(heartbeatTimer.value);
|
|
heartbeatTimer.value = null;
|
|
}
|
|
};
|
|
|
|
const pushStoreEventListenerRemovalFn = ref<(() => void) | null>(null);
|
|
|
|
function initialize() {
|
|
if (pushStoreEventListenerRemovalFn.value) {
|
|
return;
|
|
}
|
|
|
|
pushStoreEventListenerRemovalFn.value = pushStore.addEventListener((event) => {
|
|
if (
|
|
event.type === 'collaboratorsChanged' &&
|
|
event.data.workflowId === workflowsStore.workflowId
|
|
) {
|
|
collaborators.value = event.data.collaborators;
|
|
}
|
|
});
|
|
|
|
addBeforeUnloadEventBindings();
|
|
notifyWorkflowOpened();
|
|
startHeartbeat();
|
|
}
|
|
|
|
function terminate() {
|
|
if (typeof pushStoreEventListenerRemovalFn.value === 'function') {
|
|
pushStoreEventListenerRemovalFn.value();
|
|
pushStoreEventListenerRemovalFn.value = null;
|
|
}
|
|
notifyWorkflowClosed();
|
|
stopHeartbeat();
|
|
pushStore.clearQueue();
|
|
removeBeforeUnloadEventBindings();
|
|
if (unloadTimeout.value) {
|
|
clearTimeout(unloadTimeout.value);
|
|
}
|
|
}
|
|
|
|
function notifyWorkflowOpened() {
|
|
const { workflowId } = workflowsStore;
|
|
if (workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) return;
|
|
pushStore.send({ type: 'workflowOpened', workflowId });
|
|
}
|
|
|
|
function notifyWorkflowClosed() {
|
|
const { workflowId } = workflowsStore;
|
|
if (workflowId === PLACEHOLDER_EMPTY_WORKFLOW_ID) return;
|
|
pushStore.send({ type: 'workflowClosed', workflowId });
|
|
|
|
collaborators.value = collaborators.value.filter(
|
|
({ user }) => user.id !== usersStore.currentUserId,
|
|
);
|
|
}
|
|
|
|
return {
|
|
collaborators,
|
|
initialize,
|
|
terminate,
|
|
startHeartbeat,
|
|
stopHeartbeat,
|
|
};
|
|
});
|