2024-10-04 00:28:53 -07:00
|
|
|
import { InstanceSettings } from 'n8n-core';
|
2024-10-11 01:31:33 -07:00
|
|
|
import { ensureError } from 'n8n-workflow';
|
2024-10-04 00:28:53 -07:00
|
|
|
import { Service } from 'typedi';
|
|
|
|
|
2024-10-11 01:31:33 -07:00
|
|
|
import { ActiveWorkflowManager } from '@/active-workflow-manager';
|
|
|
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
2024-10-04 00:28:53 -07:00
|
|
|
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
|
|
|
import { EventService } from '@/events/event.service';
|
|
|
|
import type { PubSubEventMap } from '@/events/maps/pub-sub.event-map';
|
|
|
|
import { ExternalSecretsManager } from '@/external-secrets/external-secrets-manager.ee';
|
|
|
|
import { License } from '@/license';
|
2024-10-11 01:31:33 -07:00
|
|
|
import { Push } from '@/push';
|
2024-10-07 07:19:58 -07:00
|
|
|
import { Publisher } from '@/scaling/pubsub/publisher.service';
|
2024-10-04 00:28:53 -07:00
|
|
|
import { CommunityPackagesService } from '@/services/community-packages.service';
|
2024-10-07 07:19:58 -07:00
|
|
|
import { assertNever } from '@/utils';
|
2024-10-11 01:31:33 -07:00
|
|
|
import { TestWebhooks } from '@/webhooks/test-webhooks';
|
2024-10-07 07:19:58 -07:00
|
|
|
|
2024-10-11 01:31:33 -07:00
|
|
|
import type { PubSub } from './pubsub.types';
|
|
|
|
import { WorkerStatusService } from '../worker-status.service';
|
2024-10-04 00:28:53 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Responsible for handling events emitted from messages received via a pubsub channel.
|
|
|
|
*/
|
|
|
|
@Service()
|
|
|
|
export class PubSubHandler {
|
|
|
|
constructor(
|
|
|
|
private readonly eventService: EventService,
|
|
|
|
private readonly instanceSettings: InstanceSettings,
|
|
|
|
private readonly license: License,
|
|
|
|
private readonly eventbus: MessageEventBus,
|
|
|
|
private readonly externalSecretsManager: ExternalSecretsManager,
|
|
|
|
private readonly communityPackagesService: CommunityPackagesService,
|
2024-10-07 07:19:58 -07:00
|
|
|
private readonly publisher: Publisher,
|
2024-10-11 01:31:33 -07:00
|
|
|
private readonly workerStatusService: WorkerStatusService,
|
|
|
|
private readonly activeWorkflowManager: ActiveWorkflowManager,
|
|
|
|
private readonly push: Push,
|
|
|
|
private readonly workflowRepository: WorkflowRepository,
|
|
|
|
private readonly testWebhooks: TestWebhooks,
|
2024-10-04 00:28:53 -07:00
|
|
|
) {}
|
|
|
|
|
|
|
|
init() {
|
2024-10-07 07:19:58 -07:00
|
|
|
switch (this.instanceSettings.instanceType) {
|
|
|
|
case 'webhook':
|
|
|
|
this.setupHandlers(this.commonHandlers);
|
|
|
|
break;
|
|
|
|
case 'worker':
|
|
|
|
this.setupHandlers({
|
|
|
|
...this.commonHandlers,
|
|
|
|
'get-worker-status': async () =>
|
|
|
|
await this.publisher.publishWorkerResponse({
|
2024-10-15 05:55:13 -07:00
|
|
|
senderId: this.instanceSettings.hostId,
|
2024-10-11 01:31:33 -07:00
|
|
|
response: 'response-to-get-worker-status',
|
|
|
|
payload: this.workerStatusService.generateStatus(),
|
2024-10-07 07:19:58 -07:00
|
|
|
}),
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case 'main':
|
2024-10-11 01:31:33 -07:00
|
|
|
this.setupHandlers({
|
|
|
|
...this.commonHandlers,
|
|
|
|
...this.multiMainHandlers,
|
|
|
|
'response-to-get-worker-status': async (payload) =>
|
|
|
|
this.push.broadcast('sendWorkerStatusMessage', {
|
|
|
|
workerId: payload.senderId,
|
|
|
|
status: payload,
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
|
2024-10-07 07:19:58 -07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assertNever(this.instanceSettings.instanceType);
|
|
|
|
}
|
2024-10-04 00:28:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private setupHandlers<EventNames extends keyof PubSubEventMap>(
|
|
|
|
map: {
|
|
|
|
[EventName in EventNames]?: (event: PubSubEventMap[EventName]) => void | Promise<void>;
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
for (const [eventName, handlerFn] of Object.entries(map) as Array<
|
|
|
|
[EventNames, (event: PubSubEventMap[EventNames]) => void | Promise<void>]
|
|
|
|
>) {
|
|
|
|
this.eventService.on(eventName, async (event) => {
|
|
|
|
await handlerFn(event);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-07 07:19:58 -07:00
|
|
|
private commonHandlers: {
|
2024-10-11 01:31:33 -07:00
|
|
|
[EventName in keyof PubSub.CommonEvents]: (event: PubSubEventMap[EventName]) => Promise<void>;
|
2024-10-07 07:19:58 -07:00
|
|
|
} = {
|
|
|
|
'reload-license': async () => await this.license.reload(),
|
|
|
|
'restart-event-bus': async () => await this.eventbus.restart(),
|
|
|
|
'reload-external-secrets-providers': async () =>
|
|
|
|
await this.externalSecretsManager.reloadAllProviders(),
|
|
|
|
'community-package-install': async ({ packageName, packageVersion }) =>
|
|
|
|
await this.communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion),
|
|
|
|
'community-package-update': async ({ packageName, packageVersion }) =>
|
|
|
|
await this.communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion),
|
|
|
|
'community-package-uninstall': async ({ packageName }) =>
|
|
|
|
await this.communityPackagesService.removeNpmPackage(packageName),
|
|
|
|
};
|
2024-10-11 01:31:33 -07:00
|
|
|
|
|
|
|
private multiMainHandlers: {
|
|
|
|
[EventName in keyof PubSub.MultiMainEvents]: (
|
|
|
|
event: PubSubEventMap[EventName],
|
|
|
|
) => Promise<void>;
|
|
|
|
} = {
|
|
|
|
'add-webhooks-triggers-and-pollers': async ({ workflowId }) => {
|
|
|
|
if (this.instanceSettings.isFollower) return;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.activeWorkflowManager.add(workflowId, 'activate', undefined, {
|
|
|
|
shouldPublish: false, // prevent leader from re-publishing message
|
|
|
|
});
|
|
|
|
|
|
|
|
this.push.broadcast('workflowActivated', { workflowId });
|
|
|
|
|
|
|
|
await this.publisher.publishCommand({
|
|
|
|
command: 'display-workflow-activation',
|
|
|
|
payload: { workflowId },
|
|
|
|
}); // instruct followers to show activation in UI
|
|
|
|
} catch (e) {
|
|
|
|
const error = ensureError(e);
|
|
|
|
const { message } = error;
|
|
|
|
|
|
|
|
await this.workflowRepository.update(workflowId, { active: false });
|
|
|
|
|
|
|
|
this.push.broadcast('workflowFailedToActivate', { workflowId, errorMessage: message });
|
|
|
|
|
|
|
|
await this.publisher.publishCommand({
|
|
|
|
command: 'display-workflow-activation-error',
|
|
|
|
payload: { workflowId, errorMessage: message },
|
|
|
|
}); // instruct followers to show activation error in UI
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'remove-triggers-and-pollers': async ({ workflowId }) => {
|
|
|
|
if (this.instanceSettings.isFollower) return;
|
|
|
|
|
|
|
|
await this.activeWorkflowManager.removeActivationError(workflowId);
|
|
|
|
await this.activeWorkflowManager.removeWorkflowTriggersAndPollers(workflowId);
|
|
|
|
|
|
|
|
this.push.broadcast('workflowDeactivated', { workflowId });
|
|
|
|
|
|
|
|
// instruct followers to show workflow deactivation in UI
|
|
|
|
await this.publisher.publishCommand({
|
|
|
|
command: 'display-workflow-deactivation',
|
|
|
|
payload: { workflowId },
|
|
|
|
});
|
|
|
|
},
|
|
|
|
'display-workflow-activation': async ({ workflowId }) =>
|
|
|
|
this.push.broadcast('workflowActivated', { workflowId }),
|
|
|
|
'display-workflow-deactivation': async ({ workflowId }) =>
|
|
|
|
this.push.broadcast('workflowDeactivated', { workflowId }),
|
|
|
|
'display-workflow-activation-error': async ({ workflowId, errorMessage }) =>
|
|
|
|
this.push.broadcast('workflowFailedToActivate', { workflowId, errorMessage }),
|
|
|
|
'relay-execution-lifecycle-event': async ({ type, args, pushRef }) => {
|
|
|
|
if (!this.push.getBackend().hasPushRef(pushRef)) return;
|
|
|
|
|
|
|
|
this.push.send(type, args, pushRef);
|
|
|
|
},
|
|
|
|
'clear-test-webhooks': async ({ webhookKey, workflowEntity, pushRef }) => {
|
|
|
|
if (!this.push.getBackend().hasPushRef(pushRef)) return;
|
|
|
|
|
|
|
|
this.testWebhooks.clearTimeout(webhookKey);
|
|
|
|
|
|
|
|
const workflow = this.testWebhooks.toWorkflow(workflowEntity);
|
|
|
|
|
|
|
|
await this.testWebhooks.deactivateWebhooks(workflow);
|
|
|
|
},
|
|
|
|
};
|
2024-10-04 00:28:53 -07:00
|
|
|
}
|