@friggframework/core 2.0.0-next.9 → 2.0.0-next.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +702 -0
- package/README.md +959 -50
- package/application/commands/README.md +451 -0
- package/application/commands/credential-commands.js +245 -0
- package/application/commands/entity-commands.js +336 -0
- package/application/commands/integration-commands.js +271 -0
- package/application/commands/scheduler-commands.js +263 -0
- package/application/commands/user-commands.js +283 -0
- package/application/index.js +73 -0
- package/assertions/index.js +0 -3
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +60 -24
- package/core/create-handler.js +84 -6
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +269 -0
- package/credential/repositories/credential-repository-postgres.js +287 -0
- package/credential/repositories/credential-repository.js +300 -0
- package/credential/use-cases/get-credential-for-user.js +25 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +154 -0
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +839 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +401 -0
- package/database/encryption/field-encryption-service.js +254 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/prisma-encryption-extension.js +230 -0
- package/database/index.js +21 -21
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +138 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-health-use-case.js +29 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +83 -0
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/run-database-migration-use-case.js +139 -0
- package/database/use-cases/test-encryption-use-case.js +253 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +94 -0
- package/database/utils/mongodb-schema-init.js +108 -0
- package/database/utils/prisma-runner.js +477 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/encrypt/Cryptor.js +34 -168
- package/encrypt/index.js +1 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +15 -7
- package/errors/index.js +2 -0
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +335 -0
- package/generated/prisma-mongodb/index-browser.js +317 -0
- package/generated/prisma-mongodb/index.d.ts +22955 -0
- package/generated/prisma-mongodb/index.js +360 -0
- package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/library.js +146 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +368 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +342 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +357 -0
- package/generated/prisma-postgresql/index-browser.js +339 -0
- package/generated/prisma-postgresql/index.d.ts +25135 -0
- package/generated/prisma-postgresql/index.js +382 -0
- package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/library.js +146 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +351 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +364 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +57 -0
- package/handlers/backend-utils.js +297 -0
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/integration-event-dispatcher.js +54 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +15 -0
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +326 -0
- package/handlers/routers/health.js +518 -0
- package/handlers/routers/integration-defined-routers.js +117 -0
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/routers/user.js +63 -0
- package/handlers/routers/websocket.js +57 -0
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
- package/handlers/workers/db-migration.js +352 -0
- package/handlers/workers/dlq-processor.js +63 -0
- package/handlers/workers/integration-defined-workers.js +30 -0
- package/index.js +82 -46
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
- package/infrastructure/scheduler/index.js +33 -0
- package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
- package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
- package/integrations/EXTENSIONS.md +240 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/extension.js +254 -0
- package/integrations/index.js +20 -10
- package/integrations/integration-base.js +487 -55
- package/integrations/integration-router.js +396 -179
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-documentdb.js +219 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +144 -0
- package/integrations/repositories/integration-repository-mongo.js +330 -0
- package/integrations/repositories/integration-repository-postgres.js +385 -0
- package/integrations/repositories/process-repository-documentdb.js +311 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +136 -0
- package/integrations/repositories/process-repository-mongo.js +262 -0
- package/integrations/repositories/process-repository-postgres.js +380 -0
- package/integrations/repositories/process-update-ops-shared.js +112 -0
- package/integrations/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +105 -0
- package/integrations/tests/doubles/test-integration-repository.js +112 -0
- package/integrations/use-cases/create-integration.js +83 -0
- package/integrations/use-cases/create-process.js +128 -0
- package/integrations/use-cases/delete-integration-for-user.js +101 -0
- package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +88 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/get-process.js +87 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +92 -0
- package/integrations/use-cases/update-process-metrics.js +214 -0
- package/integrations/use-cases/update-process-state.js +158 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/logs/logger.js +0 -4
- package/{module-plugin → modules}/index.js +0 -10
- package/modules/module-factory.js +56 -0
- package/modules/module.js +274 -0
- package/modules/repositories/module-repository-documentdb.js +350 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +145 -0
- package/modules/repositories/module-repository-mongo.js +436 -0
- package/modules/repositories/module-repository-postgres.js +481 -0
- package/modules/repositories/module-repository.js +369 -0
- package/modules/requester/api-key.js +52 -0
- package/modules/requester/oauth-2.js +396 -0
- package/modules/requester/requester.js +280 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +71 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +34 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +243 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +82 -50
- package/prisma-mongodb/schema.prisma +368 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
- package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +351 -0
- package/queues/queuer-util.js +103 -21
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/associations/index.d.ts +0 -17
- package/types/core/index.d.ts +12 -4
- package/types/database/index.d.ts +10 -2
- package/types/encrypt/index.d.ts +5 -3
- package/types/integrations/index.d.ts +3 -8
- package/types/module-plugin/index.d.ts +17 -69
- package/types/syncs/index.d.ts +0 -17
- package/user/repositories/user-repository-documentdb.js +441 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +201 -0
- package/user/repositories/user-repository-mongo.js +308 -0
- package/user/repositories/user-repository-postgres.js +360 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
- package/websocket/repositories/websocket-connection-repository.js +161 -0
- package/assertions/is-equal.js +0 -17
- package/associations/model.js +0 -54
- package/database/models/IndividualUser.js +0 -76
- package/database/models/OrganizationUser.js +0 -29
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/models/UserModel.js +0 -7
- package/database/models/WebsocketConnection.js +0 -49
- package/database/mongo.js +0 -45
- package/database/mongoose.js +0 -5
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- package/encrypt/test-encrypt.js +0 -107
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/entity.js +0 -46
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/oauth-2.js +0 -219
- package/module-plugin/requester/requester.js +0 -165
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- package/syncs/model.js +0 -62
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
createIntegrationMappingRepository,
|
|
3
|
+
} = require('./repositories/integration-mapping-repository-factory');
|
|
2
4
|
const { Options } = require('./options');
|
|
5
|
+
const {
|
|
6
|
+
UpdateIntegrationStatus,
|
|
7
|
+
} = require('./use-cases/update-integration-status');
|
|
8
|
+
const {
|
|
9
|
+
createIntegrationRepository,
|
|
10
|
+
} = require('./repositories/integration-repository-factory');
|
|
11
|
+
const {
|
|
12
|
+
UpdateIntegrationMessages,
|
|
13
|
+
} = require('./use-cases/update-integration-messages');
|
|
14
|
+
const { validateExtensionBinding } = require('./extension');
|
|
15
|
+
|
|
3
16
|
const constantsToBeMigrated = {
|
|
4
17
|
defaultEvents: {
|
|
5
18
|
ON_CREATE: 'ON_CREATE',
|
|
@@ -10,6 +23,8 @@ const constantsToBeMigrated = {
|
|
|
10
23
|
GET_USER_ACTIONS: 'GET_USER_ACTIONS',
|
|
11
24
|
GET_USER_ACTION_OPTIONS: 'GET_USER_ACTION_OPTIONS',
|
|
12
25
|
REFRESH_USER_ACTION_OPTIONS: 'REFRESH_USER_ACTION_OPTIONS',
|
|
26
|
+
WEBHOOK_RECEIVED: 'WEBHOOK_RECEIVED', // HTTP handler, no DB
|
|
27
|
+
ON_WEBHOOK: 'ON_WEBHOOK', // Queue worker, DB-connected
|
|
13
28
|
// etc...
|
|
14
29
|
},
|
|
15
30
|
types: {
|
|
@@ -19,6 +34,16 @@ const constantsToBeMigrated = {
|
|
|
19
34
|
};
|
|
20
35
|
|
|
21
36
|
class IntegrationBase {
|
|
37
|
+
// todo: maybe we can pass this as Dependency Injection in the sub-class constructor
|
|
38
|
+
integrationRepository = createIntegrationRepository();
|
|
39
|
+
integrationMappingRepository = createIntegrationMappingRepository();
|
|
40
|
+
updateIntegrationStatus = new UpdateIntegrationStatus({
|
|
41
|
+
integrationRepository: this.integrationRepository,
|
|
42
|
+
});
|
|
43
|
+
updateIntegrationMessages = new UpdateIntegrationMessages({
|
|
44
|
+
integrationRepository: this.integrationRepository,
|
|
45
|
+
});
|
|
46
|
+
|
|
22
47
|
static getOptionDetails() {
|
|
23
48
|
const options = new Options({
|
|
24
49
|
module: Object.values(this.Definition.modules)[0], // This is a placeholder until we revamp the frontend
|
|
@@ -26,6 +51,7 @@ class IntegrationBase {
|
|
|
26
51
|
});
|
|
27
52
|
return options.get();
|
|
28
53
|
}
|
|
54
|
+
|
|
29
55
|
/**
|
|
30
56
|
* CHILDREN SHOULD SPECIFY A DEFINITION FOR THE INTEGRATION
|
|
31
57
|
*/
|
|
@@ -35,6 +61,9 @@ class IntegrationBase {
|
|
|
35
61
|
supportedVersions: [], // Eventually usable for deprecation and future test version purposes
|
|
36
62
|
|
|
37
63
|
modules: {},
|
|
64
|
+
// Tier 3 Integration Extensions — see packages/core/integrations/EXTENSIONS.md
|
|
65
|
+
// Shape: { [bindingName]: { extension, handlers?: { [eventName]: methodName } } }
|
|
66
|
+
extensions: {},
|
|
38
67
|
display: {
|
|
39
68
|
name: 'Integration Name',
|
|
40
69
|
logo: '',
|
|
@@ -50,29 +79,30 @@ class IntegrationBase {
|
|
|
50
79
|
static getCurrentVersion() {
|
|
51
80
|
return this.Definition.version;
|
|
52
81
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
|
|
83
|
+
// REMOVED: registerEventHandlers() - Event handling is now done by IntegrationEventDispatcher
|
|
84
|
+
|
|
85
|
+
constructor(params = {}) {
|
|
86
|
+
this.modules = {};
|
|
87
|
+
this.events = this.events || {};
|
|
88
|
+
this.messages = { errors: [], warnings: [] };
|
|
89
|
+
this._isHydrated = false;
|
|
90
|
+
|
|
91
|
+
if (params && Object.keys(params).length > 0) {
|
|
92
|
+
this.setIntegrationRecord({
|
|
93
|
+
record: {
|
|
94
|
+
id: params.id,
|
|
95
|
+
userId: params.userId,
|
|
96
|
+
entities: params.entities,
|
|
97
|
+
config: params.config,
|
|
98
|
+
status: params.status,
|
|
99
|
+
version: params.version,
|
|
100
|
+
messages: params.messages,
|
|
101
|
+
},
|
|
102
|
+
modules: params.modules || [],
|
|
103
|
+
});
|
|
66
104
|
}
|
|
67
|
-
}
|
|
68
|
-
registerEventHandlers() {
|
|
69
|
-
this.on = {
|
|
70
|
-
...this.defaultEvents,
|
|
71
|
-
...this.events,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
105
|
|
|
75
|
-
constructor(params) {
|
|
76
106
|
this.defaultEvents = {
|
|
77
107
|
[constantsToBeMigrated.defaultEvents.ON_CREATE]: {
|
|
78
108
|
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
@@ -106,22 +136,139 @@ class IntegrationBase {
|
|
|
106
136
|
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
107
137
|
handler: this.refreshActionOptions,
|
|
108
138
|
},
|
|
139
|
+
[constantsToBeMigrated.defaultEvents.WEBHOOK_RECEIVED]: {
|
|
140
|
+
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
141
|
+
handler: this.onWebhookReceived,
|
|
142
|
+
},
|
|
143
|
+
[constantsToBeMigrated.defaultEvents.ON_WEBHOOK]: {
|
|
144
|
+
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
145
|
+
handler: this.onWebhook,
|
|
146
|
+
},
|
|
109
147
|
};
|
|
110
|
-
this.loadModules();
|
|
111
148
|
}
|
|
112
149
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
150
|
+
// todo: debate wether we want to keep this pattern to set the record or not.
|
|
151
|
+
/**
|
|
152
|
+
* Persist the database record and module instances onto this integration instance.
|
|
153
|
+
* Accepts either a plain object containing the persisted fields or an object with
|
|
154
|
+
* a `record` property plus a `modules` collection.
|
|
155
|
+
* @param {Object} payload
|
|
156
|
+
* @param {Object} [payload.record]
|
|
157
|
+
* @param {Array} [payload.modules]
|
|
158
|
+
*/
|
|
159
|
+
setIntegrationRecord(payload = {}) {
|
|
160
|
+
if (!payload || Object.keys(payload).length === 0) {
|
|
161
|
+
throw new Error('setIntegrationRecord requires integration data');
|
|
118
162
|
}
|
|
119
|
-
|
|
163
|
+
|
|
164
|
+
const integrationRecord = payload.record;
|
|
165
|
+
const integrationModules = payload.modules ?? [];
|
|
166
|
+
|
|
167
|
+
if (!integrationRecord) {
|
|
168
|
+
throw new Error('Integration record not provided');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { id, userId, entities, config, status, version, messages } =
|
|
172
|
+
integrationRecord;
|
|
173
|
+
|
|
174
|
+
this.id = id;
|
|
175
|
+
this.userId = userId;
|
|
176
|
+
this.entities = entities;
|
|
177
|
+
this.config = config;
|
|
178
|
+
this.status = status;
|
|
179
|
+
this.version = version;
|
|
180
|
+
this.messages = messages || { errors: [], warnings: [] };
|
|
181
|
+
|
|
182
|
+
this.modules = this._appendModules(integrationModules);
|
|
183
|
+
|
|
184
|
+
this.record = {
|
|
185
|
+
id: this.id,
|
|
186
|
+
userId: this.userId,
|
|
187
|
+
entities: this.entities,
|
|
188
|
+
config: this.config,
|
|
189
|
+
status: this.status,
|
|
190
|
+
version: this.version,
|
|
191
|
+
messages: this.messages,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
this._isHydrated = Boolean(this.id);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get isHydrated() {
|
|
199
|
+
return this._isHydrated;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
assertHydrated(message = 'Integration instance is not hydrated') {
|
|
203
|
+
if (!this.isHydrated) {
|
|
204
|
+
throw new Error(message);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Returns the modules as object with keys as module names.
|
|
210
|
+
* Uses the keys from Definition.modules to attach modules correctly.
|
|
211
|
+
*
|
|
212
|
+
* Example:
|
|
213
|
+
* Definition.modules = { attio: {...}, quo: { definition: { getName: () => 'quo-attio' } } }
|
|
214
|
+
* Module with getName()='quo-attio' gets attached as this.quo (not this['quo-attio'])
|
|
215
|
+
*
|
|
216
|
+
* @private
|
|
217
|
+
* @param {Array} integrationModules - Array of module instances
|
|
218
|
+
* @returns {Object} The modules object
|
|
219
|
+
*/
|
|
220
|
+
_appendModules(integrationModules) {
|
|
221
|
+
const modules = {};
|
|
222
|
+
|
|
223
|
+
// Build reverse mapping: definition.getName() → referenceKey
|
|
224
|
+
// e.g., 'quo-attio' → 'quo', 'attio' → 'attio'
|
|
225
|
+
const moduleNameToKey = {};
|
|
226
|
+
if (this.constructor.Definition?.modules) {
|
|
227
|
+
for (const [key, moduleConfig] of Object.entries(this.constructor.Definition.modules)) {
|
|
228
|
+
const definition = moduleConfig.definition;
|
|
229
|
+
if (definition) {
|
|
230
|
+
// Use getName() if available, fallback to moduleName
|
|
231
|
+
const definitionName = typeof definition.getName === 'function'
|
|
232
|
+
? definition.getName()
|
|
233
|
+
: definition.moduleName;
|
|
234
|
+
if (definitionName) {
|
|
235
|
+
moduleNameToKey[definitionName] = key;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const module of integrationModules) {
|
|
242
|
+
const moduleName =
|
|
243
|
+
typeof module.getName === 'function'
|
|
244
|
+
? module.getName()
|
|
245
|
+
: module.name;
|
|
246
|
+
|
|
247
|
+
// Use the reference key from Definition.modules if available,
|
|
248
|
+
// otherwise fall back to moduleName
|
|
249
|
+
const key = moduleNameToKey[moduleName] || moduleName;
|
|
250
|
+
|
|
251
|
+
if (key) {
|
|
252
|
+
modules[key] = module;
|
|
253
|
+
this[key] = module;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Wire the Delegate pattern so Module can notify this integration
|
|
257
|
+
// of events it cannot handle itself (e.g. credential invalidation
|
|
258
|
+
// needing an Integration.status flip). Without this, Module.notify
|
|
259
|
+
// silently no-ops and Integration.status never updates on auth
|
|
260
|
+
// failure.
|
|
261
|
+
if (module && typeof module === 'object') {
|
|
262
|
+
module.delegate = this;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return modules;
|
|
120
267
|
}
|
|
121
268
|
|
|
122
269
|
async validateConfig() {
|
|
123
270
|
const configOptions = await this.getConfigOptions();
|
|
124
|
-
const currentConfig = this.
|
|
271
|
+
const currentConfig = this.getConfig();
|
|
125
272
|
let needsConfig = false;
|
|
126
273
|
for (const option of configOptions) {
|
|
127
274
|
if (option.required) {
|
|
@@ -133,56 +280,62 @@ class IntegrationBase {
|
|
|
133
280
|
)
|
|
134
281
|
) {
|
|
135
282
|
needsConfig = true;
|
|
136
|
-
this.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
283
|
+
await this.updateIntegrationMessages.execute(
|
|
284
|
+
this.id,
|
|
285
|
+
'warnings',
|
|
286
|
+
'Config Validation Error',
|
|
287
|
+
`Missing required field of ${option.label}`,
|
|
288
|
+
Date.now()
|
|
289
|
+
);
|
|
141
290
|
}
|
|
142
291
|
}
|
|
143
292
|
}
|
|
144
293
|
if (needsConfig) {
|
|
145
|
-
this.
|
|
146
|
-
await this.record.save();
|
|
294
|
+
await this.updateIntegrationStatus.execute(this.id, 'NEEDS_CONFIG');
|
|
147
295
|
}
|
|
148
296
|
}
|
|
149
297
|
|
|
150
298
|
async testAuth() {
|
|
151
299
|
let didAuthPass = true;
|
|
152
300
|
|
|
153
|
-
for (const module of Object.keys(
|
|
301
|
+
for (const module of Object.keys(this.constructor.Definition.modules)) {
|
|
154
302
|
try {
|
|
155
303
|
await this[module].testAuth();
|
|
156
304
|
} catch {
|
|
157
305
|
didAuthPass = false;
|
|
158
|
-
this.
|
|
159
|
-
|
|
160
|
-
|
|
306
|
+
await this.updateIntegrationMessages.execute(
|
|
307
|
+
this.id,
|
|
308
|
+
'errors',
|
|
309
|
+
'Authentication Error',
|
|
310
|
+
`There was an error with your ${this[
|
|
161
311
|
module
|
|
162
312
|
].constructor.getName()} Entity.
|
|
163
313
|
Please reconnect/re-authenticate, or reach out to Support for assistance.`,
|
|
164
|
-
|
|
165
|
-
|
|
314
|
+
Date.now()
|
|
315
|
+
);
|
|
166
316
|
}
|
|
167
317
|
}
|
|
168
318
|
|
|
169
319
|
if (!didAuthPass) {
|
|
170
|
-
this.
|
|
171
|
-
this.record.markModified('messages.error');
|
|
172
|
-
await this.record.save();
|
|
320
|
+
await this.updateIntegrationStatus.execute(this.id, 'ERROR');
|
|
173
321
|
}
|
|
174
322
|
}
|
|
175
323
|
|
|
176
324
|
async getMapping(sourceId) {
|
|
177
|
-
|
|
325
|
+
// todo: not sure we should call the repository directly from here
|
|
326
|
+
return this.integrationMappingRepository.findMappingBy(
|
|
327
|
+
this.id,
|
|
328
|
+
sourceId
|
|
329
|
+
);
|
|
178
330
|
}
|
|
179
331
|
|
|
180
332
|
async upsertMapping(sourceId, mapping) {
|
|
181
333
|
if (!sourceId) {
|
|
182
334
|
throw new Error(`sourceId must be set`);
|
|
183
335
|
}
|
|
184
|
-
|
|
185
|
-
|
|
336
|
+
// todo: not sure we should call the repository directly from here
|
|
337
|
+
return await this.integrationMappingRepository.upsertMapping(
|
|
338
|
+
this.id,
|
|
186
339
|
sourceId,
|
|
187
340
|
mapping
|
|
188
341
|
);
|
|
@@ -191,13 +344,13 @@ class IntegrationBase {
|
|
|
191
344
|
/**
|
|
192
345
|
* CHILDREN CAN OVERRIDE THESE CONFIGURATION METHODS
|
|
193
346
|
*/
|
|
194
|
-
async onCreate(
|
|
195
|
-
this.
|
|
196
|
-
await this.record.save();
|
|
197
|
-
return this.record;
|
|
347
|
+
async onCreate({ integrationId }) {
|
|
348
|
+
await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
|
|
198
349
|
}
|
|
199
350
|
|
|
200
|
-
async onUpdate(params) {
|
|
351
|
+
async onUpdate(params) {
|
|
352
|
+
return this.validateConfig();
|
|
353
|
+
}
|
|
201
354
|
|
|
202
355
|
async onDelete(params) {}
|
|
203
356
|
|
|
@@ -224,7 +377,6 @@ class IntegrationBase {
|
|
|
224
377
|
return {};
|
|
225
378
|
}
|
|
226
379
|
async loadUserActions({ actionType } = {}) {
|
|
227
|
-
console.log('loadUserActions called with actionType:', actionType);
|
|
228
380
|
const userActions = {};
|
|
229
381
|
for (const [key, event] of Object.entries(this.events)) {
|
|
230
382
|
if (event.type === constantsToBeMigrated.types.USER_ACTION) {
|
|
@@ -259,6 +411,286 @@ class IntegrationBase {
|
|
|
259
411
|
};
|
|
260
412
|
return options;
|
|
261
413
|
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* WEBHOOK EVENT HANDLERS
|
|
417
|
+
*/
|
|
418
|
+
async onWebhookReceived({ req, res }) {
|
|
419
|
+
// Default: queue webhook for processing
|
|
420
|
+
const body = req.body;
|
|
421
|
+
const integrationId = req.params.integrationId || null;
|
|
422
|
+
|
|
423
|
+
await this.queueWebhook({
|
|
424
|
+
integrationId,
|
|
425
|
+
body,
|
|
426
|
+
headers: req.headers,
|
|
427
|
+
query: req.query,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
res.status(200).json({ received: true });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async onWebhook({ data }) {
|
|
434
|
+
// Default: no-op, integrations override this
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Queue a webhook for asynchronous worker dispatch.
|
|
439
|
+
*
|
|
440
|
+
* The dispatch event defaults to `ON_WEBHOOK` for backward compatibility
|
|
441
|
+
* with the `Definition.webhooks: true` path. Extensions (and any caller
|
|
442
|
+
* that needs the worker to invoke a specific bound handler) can override
|
|
443
|
+
* by passing `event` in the payload — it's stripped from the payload and
|
|
444
|
+
* used as the SQS message's dispatch event.
|
|
445
|
+
*
|
|
446
|
+
* @param {Object} data - Webhook payload. May include `event` to override
|
|
447
|
+
* the default `ON_WEBHOOK` dispatch event. All other fields are passed
|
|
448
|
+
* through to the worker as the `data` field of the SQS message.
|
|
449
|
+
*/
|
|
450
|
+
async queueWebhook(data) {
|
|
451
|
+
const { QueuerUtil } = require('../queues');
|
|
452
|
+
|
|
453
|
+
const queueName = `${this.constructor.Definition.name
|
|
454
|
+
.toUpperCase()
|
|
455
|
+
.replace(/-/g, '_')}_QUEUE_URL`;
|
|
456
|
+
const queueUrl = process.env[queueName];
|
|
457
|
+
|
|
458
|
+
if (!queueUrl) {
|
|
459
|
+
throw new Error(`Queue URL not found for ${queueName}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const { event: dispatchEvent, ...payload } = data || {};
|
|
463
|
+
|
|
464
|
+
return QueuerUtil.send(
|
|
465
|
+
{
|
|
466
|
+
event: dispatchEvent || 'ON_WEBHOOK',
|
|
467
|
+
data: payload,
|
|
468
|
+
},
|
|
469
|
+
queueUrl
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// === Domain Methods (moved from Integration.js) ===
|
|
474
|
+
|
|
475
|
+
getConfig() {
|
|
476
|
+
return this.config;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
getModule(key) {
|
|
480
|
+
return this.modules[key];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
setModule(key, module) {
|
|
484
|
+
this.modules[key] = module;
|
|
485
|
+
this[key] = module;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
addError(error) {
|
|
489
|
+
if (!this.messages.errors) {
|
|
490
|
+
this.messages.errors = [];
|
|
491
|
+
}
|
|
492
|
+
this.messages.errors.push(error);
|
|
493
|
+
this.status = 'ERROR';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
addWarning(warning) {
|
|
497
|
+
if (!this.messages.warnings) {
|
|
498
|
+
this.messages.warnings = [];
|
|
499
|
+
}
|
|
500
|
+
this.messages.warnings.push(warning);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
isActive() {
|
|
504
|
+
return this.status === 'ENABLED' || this.status === 'ACTIVE';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
needsConfiguration() {
|
|
508
|
+
return this.status === 'NEEDS_CONFIG';
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
hasErrors() {
|
|
512
|
+
return this.status === 'ERROR';
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
belongsToUser(userId) {
|
|
516
|
+
return this.userId.toString() === userId.toString();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
registerEventHandlers() {
|
|
520
|
+
this.on = {
|
|
521
|
+
...this.defaultEvents,
|
|
522
|
+
...this.events,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Merge Tier 3 Integration Extension events into `this.events`.
|
|
528
|
+
*
|
|
529
|
+
* For each binding declared on `static Definition.extensions`, this method:
|
|
530
|
+
* 1. Validates the extension bundle shape and binding handlers
|
|
531
|
+
* 2. For each event the extension declares, resolves the handler in priority order:
|
|
532
|
+
* a. Subclass-defined `this.events[eventName]` (set in the constructor) — wins; if a
|
|
533
|
+
* binding tried to override that event with `handlers`, we log a warning so the
|
|
534
|
+
* author knows their override is shadowed.
|
|
535
|
+
* b. A method-name string in `binding.handlers[eventName]` → method on this instance
|
|
536
|
+
* c. The extension's own default `handler` function
|
|
537
|
+
* d. Otherwise throw — neither side provided a handler
|
|
538
|
+
* 3. Binds the resolved function to this instance and writes it to `this.events[eventName]`
|
|
539
|
+
*
|
|
540
|
+
* Two bindings declaring the same event throw a deterministic conflict error — silent
|
|
541
|
+
* "first/last writer wins" makes routing bugs nearly impossible to diagnose.
|
|
542
|
+
*
|
|
543
|
+
* @private
|
|
544
|
+
*/
|
|
545
|
+
_mergeExtensions() {
|
|
546
|
+
const extensions = this.constructor.Definition?.extensions || {};
|
|
547
|
+
const integrationName = this.constructor.Definition?.name;
|
|
548
|
+
// Tracks which event names have been claimed by an extension binding during
|
|
549
|
+
// this merge — distinct from subclass-defined events on `this.events`.
|
|
550
|
+
const mergedByExtension = new Map();
|
|
551
|
+
|
|
552
|
+
for (const [bindingName, binding] of Object.entries(extensions)) {
|
|
553
|
+
if (!binding || typeof binding !== 'object') {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`Integration "${integrationName}" extension binding "${bindingName}" must be an object`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
const { extension, handlers = {} } = binding;
|
|
559
|
+
validateExtensionBinding(
|
|
560
|
+
extension,
|
|
561
|
+
bindingName,
|
|
562
|
+
integrationName,
|
|
563
|
+
binding
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
const extEvents = extension.events || {};
|
|
567
|
+
for (const [eventName, eventDef] of Object.entries(extEvents)) {
|
|
568
|
+
// Conflict detection: another extension binding already claimed this event name.
|
|
569
|
+
if (mergedByExtension.has(eventName)) {
|
|
570
|
+
const prev = mergedByExtension.get(eventName);
|
|
571
|
+
throw new Error(
|
|
572
|
+
`Integration "${integrationName}" extension event conflict: ` +
|
|
573
|
+
`event "${eventName}" is declared by both binding "${prev}" and binding "${bindingName}" — ` +
|
|
574
|
+
`use distinct event names per binding or omit duplicates`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Subclass shadowing: if the subclass set this.events[eventName] before initialize(),
|
|
579
|
+
// it wins. Warn if the binding tried to wire an override that's now ignored.
|
|
580
|
+
if (this.events[eventName]) {
|
|
581
|
+
if (typeof handlers[eventName] === 'string') {
|
|
582
|
+
console.warn(
|
|
583
|
+
`[Frigg] Integration "${integrationName}" binding "${bindingName}": ` +
|
|
584
|
+
`handler "${handlers[eventName]}" for event "${eventName}" is ignored because ` +
|
|
585
|
+
`this.events["${eventName}"] was already set (subclass constructor or earlier merge)`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
let fn;
|
|
592
|
+
const override = handlers[eventName];
|
|
593
|
+
if (typeof override === 'string') {
|
|
594
|
+
if (typeof this[override] !== 'function') {
|
|
595
|
+
throw new Error(
|
|
596
|
+
`Integration "${integrationName}" extension binding "${bindingName}": handler method "${override}" not found on instance`
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
fn = this[override];
|
|
600
|
+
} else if (typeof eventDef.handler === 'function') {
|
|
601
|
+
fn = eventDef.handler;
|
|
602
|
+
} else {
|
|
603
|
+
throw new Error(
|
|
604
|
+
`Extension "${extension.name}" event "${eventName}" has no default handler and binding "${bindingName}" did not provide one`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
this.events[eventName] = {
|
|
609
|
+
type: eventDef.type,
|
|
610
|
+
handler: fn.bind(this),
|
|
611
|
+
};
|
|
612
|
+
mergedByExtension.set(eventName, bindingName);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async initialize() {
|
|
618
|
+
try {
|
|
619
|
+
const additionalUserActions = await this.loadDynamicUserActions();
|
|
620
|
+
this.events = { ...this.events, ...additionalUserActions };
|
|
621
|
+
} catch (e) {
|
|
622
|
+
this.addError(e);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
this._mergeExtensions();
|
|
626
|
+
this.registerEventHandlers();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
async send(event, object) {
|
|
630
|
+
if (!this.on[event]) {
|
|
631
|
+
throw new Error(
|
|
632
|
+
`Event ${event} is not defined in the Integration event object`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
return this.on[event].handler.call(this, object);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
getOptionDetails() {
|
|
639
|
+
const options = new Options({
|
|
640
|
+
module: Object.values(this.constructor.Definition.modules)[0],
|
|
641
|
+
...this.constructor.Definition,
|
|
642
|
+
});
|
|
643
|
+
return options.get();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Legacy method for backward compatibility
|
|
647
|
+
async loadModules() {
|
|
648
|
+
// This method was used in the old architecture for loading modules
|
|
649
|
+
// In the new architecture, modules are injected via constructor
|
|
650
|
+
// For backward compatibility, this is a no-op
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Receives notifications from modules (the Delegate pattern) when
|
|
656
|
+
* something integration-level needs attention. Today this catches the
|
|
657
|
+
* `CREDENTIAL_INVALIDATED` event Module fires from `markCredentialsInvalid`
|
|
658
|
+
* and flips this integration's status to DISABLED so the queue worker
|
|
659
|
+
* stops processing further webhooks until the user re-authorizes.
|
|
660
|
+
*
|
|
661
|
+
* Modules are wired to this delegate in `_appendModules()`, which runs
|
|
662
|
+
* during `setIntegrationRecord()` — this covers every construction path
|
|
663
|
+
* (HTTP read, queue worker, create/update/delete flows, etc.).
|
|
664
|
+
*
|
|
665
|
+
* The delegate string below must match `Module.DLGT_CREDENTIAL_INVALIDATED`
|
|
666
|
+
* in `packages/core/modules/module.js`.
|
|
667
|
+
*
|
|
668
|
+
* @param {Object} notifier - The module that fired the event
|
|
669
|
+
* @param {string} delegateString - Event type string
|
|
670
|
+
* @param {Object} [object] - Optional event payload
|
|
671
|
+
* @returns {Promise<void>}
|
|
672
|
+
*/
|
|
673
|
+
async receiveNotification(notifier, delegateString, object = null) {
|
|
674
|
+
if (!this.id) return;
|
|
675
|
+
|
|
676
|
+
if (delegateString === 'CREDENTIAL_INVALIDATED') {
|
|
677
|
+
console.log(
|
|
678
|
+
`[Frigg] Module ${notifier?.name || '?'} reported invalid credentials for integration ${this.id} — marking ERROR`
|
|
679
|
+
);
|
|
680
|
+
await this.updateIntegrationStatus.execute(this.id, 'ERROR');
|
|
681
|
+
this.status = 'ERROR';
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (delegateString === 'CREDENTIAL_VALIDATED') {
|
|
686
|
+
if (this.status !== 'ERROR') return;
|
|
687
|
+
console.log(
|
|
688
|
+
`[Frigg] Module ${notifier?.name || '?'} reported valid credentials for integration ${this.id} — clearing ERROR → ENABLED`
|
|
689
|
+
);
|
|
690
|
+
await this.updateIntegrationStatus.execute(this.id, 'ENABLED');
|
|
691
|
+
this.status = 'ENABLED';
|
|
692
|
+
}
|
|
693
|
+
}
|
|
262
694
|
}
|
|
263
695
|
|
|
264
696
|
module.exports = { IntegrationBase };
|