@friggframework/core 2.0.0--canary.550.b8b9a2b.0 → 2.0.0--canary.545.e256e95.0
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/CHANGELOG.md +66 -67
- package/CLAUDE.md +148 -124
- package/README.md +165 -130
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
- package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
- package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
- package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
- package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
- package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
- package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/admin-script-commands.js +348 -0
- package/application/commands/integration-commands.js +12 -6
- package/application/index.js +5 -3
- package/assertions/get.js +17 -7
- package/associations/association.js +59 -57
- package/core/CLAUDE.md +222 -156
- package/core/Worker.js +33 -12
- package/credential/credential-router.js +262 -0
- package/credential/repositories/credential-repository-documentdb.js +71 -1
- package/credential/repositories/credential-repository-factory.js +4 -6
- package/credential/repositories/credential-repository-interface.js +8 -2
- package/credential/repositories/credential-repository-mongo.js +45 -4
- package/credential/repositories/credential-repository-postgres.js +44 -3
- package/credential/repositories/credential-repository.js +3 -1
- package/credential/use-cases/delete-credential-for-user.js +50 -0
- package/credential/use-cases/get-credential-for-user.js +21 -3
- package/credential/use-cases/list-credentials-for-user.js +31 -0
- package/credential/use-cases/reauthorize-credential.js +103 -0
- package/credential/use-cases/update-authentication-status.js +5 -2
- package/database/MONGODB_TRANSACTION_FIX.md +72 -52
- package/database/config.js +13 -59
- package/database/documentdb-encryption-service.js +29 -13
- package/database/documentdb-utils.js +21 -12
- package/database/encryption/README.md +53 -36
- package/database/encryption/encryption-schema-registry.js +67 -21
- package/database/encryption/field-encryption-service.js +15 -4
- package/database/encryption/logger.js +3 -2
- package/database/encryption/prisma-encryption-extension.js +5 -8
- package/database/prisma.js +27 -16
- package/database/repositories/health-check-repository-factory.js +7 -5
- package/database/repositories/health-check-repository-interface.js +15 -5
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/use-cases/check-database-health-use-case.js +3 -2
- package/database/use-cases/check-database-state-use-case.js +8 -7
- package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
- package/database/use-cases/get-migration-status-use-case.js +4 -2
- package/database/use-cases/run-database-migration-use-case.js +41 -9
- package/database/use-cases/test-encryption-use-case.js +4 -5
- package/database/use-cases/trigger-database-migration-use-case.js +9 -8
- package/database/utils/mongodb-collection-utils.js +11 -3
- package/database/utils/mongodb-schema-init.js +10 -4
- package/database/utils/prisma-runner.js +144 -74
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
- package/encrypt/Cryptor.js +54 -49
- package/encrypt/aes-encryption-key-provider.js +82 -0
- package/encrypt/encryption-key-provider-interface.js +50 -0
- package/errors/fetch-error.js +1 -1
- package/extensions/bootstrap-runner.js +43 -0
- package/extensions/extension-loader.js +167 -0
- package/extensions/index.js +37 -0
- package/extensions/initialize-app.js +123 -0
- package/extensions/route-mounter.js +59 -0
- package/extensions/schema-composer.js +188 -0
- package/generated/prisma-mongodb/edge.js +60 -5
- package/generated/prisma-mongodb/index-browser.js +49 -1
- package/generated/prisma-mongodb/index.d.ts +4025 -63
- package/generated/prisma-mongodb/index.js +66 -11
- package/generated/{prisma-postgresql/query-engine-debian-openssl-3.0.x → prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node} +0 -0
- package/generated/{prisma-postgresql/query-engine-rhel-openssl-3.0.x → prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node} +0 -0
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/runtime/library.js +146 -0
- package/generated/prisma-mongodb/schema.prisma +89 -2
- package/generated/prisma-mongodb/wasm.js +60 -5
- package/generated/prisma-postgresql/edge.js +59 -5
- package/generated/prisma-postgresql/index-browser.js +48 -1
- package/generated/prisma-postgresql/index.d.ts +4742 -54
- package/generated/prisma-postgresql/index.js +65 -11
- package/generated/{prisma-mongodb/query-engine-debian-openssl-3.0.x → prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node} +0 -0
- package/generated/{prisma-mongodb/query-engine-rhel-openssl-3.0.x → prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node} +0 -0
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/runtime/library.js +146 -0
- package/generated/prisma-postgresql/schema.prisma +87 -2
- package/generated/prisma-postgresql/wasm.js +59 -5
- package/handlers/WEBHOOKS.md +104 -81
- package/handlers/app-definition-loader.js +53 -10
- package/handlers/app-handler-helpers.js +14 -3
- package/handlers/backend-utils.js +3 -17
- package/handlers/database-migration-handler.js +64 -45
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +170 -135
- package/handlers/routers/admin.js +474 -0
- package/handlers/routers/auth.js +51 -9
- package/handlers/routers/db-migration.handler.js +2 -3
- package/handlers/routers/db-migration.js +102 -66
- package/handlers/routers/docs.js +180 -0
- package/handlers/routers/health.js +84 -283
- package/handlers/routers/integration-defined-routers.js +44 -33
- package/handlers/routers/integration-webhook-routers.js +73 -52
- package/handlers/routers/middleware/loadUser.js +39 -0
- package/handlers/routers/middleware/requireAdmin.js +43 -0
- package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
- package/handlers/routers/user.js +30 -14
- package/handlers/routers/websocket.js +18 -4
- package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
- package/handlers/workers/db-migration.js +74 -35
- package/handlers/workers/integration-defined-workers.js +31 -20
- package/index.js +60 -16
- package/infrastructure/scheduler/index.js +15 -11
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
- package/integrations/WEBHOOK-QUICKSTART.md +21 -14
- package/integrations/index.js +4 -0
- package/integrations/integration-base.js +9 -6
- package/integrations/integration-router.js +1841 -153
- package/integrations/options.js +47 -20
- package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
- package/integrations/repositories/integration-repository-documentdb.js +53 -24
- package/integrations/repositories/integration-repository-factory.js +6 -6
- package/integrations/repositories/integration-repository-interface.js +27 -9
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +4 -7
- package/integrations/repositories/process-repository-interface.js +9 -5
- package/integrations/repositories/process-repository-mongo.js +7 -3
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +9 -8
- package/integrations/tests/doubles/dummy-integration-class.js +81 -8
- package/integrations/tests/doubles/test-integration-repository.js +51 -11
- package/integrations/use-cases/create-integration.js +36 -32
- package/integrations/use-cases/create-process.js +20 -9
- package/integrations/use-cases/delete-integration-for-user.js +5 -4
- package/integrations/use-cases/execute-proxy-request.js +564 -0
- package/integrations/use-cases/get-integration-for-user.js +25 -10
- package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
- package/integrations/use-cases/get-integrations-for-user.js +4 -1
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +5 -6
- package/integrations/use-cases/index.js +3 -1
- package/integrations/use-cases/load-integration-context.js +1 -5
- package/integrations/use-cases/update-process-state.js +10 -8
- package/integrations/utils/map-integration-dto.js +9 -3
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +32 -30
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/domain/entities/AuthorizationSession.js +131 -0
- package/modules/domain/entities/index.js +10 -0
- package/modules/module-factory.js +1 -1
- package/modules/module.js +44 -11
- package/modules/repositories/authorization-session-repository-factory.js +53 -0
- package/modules/repositories/authorization-session-repository-interface.js +75 -0
- package/modules/repositories/authorization-session-repository-mongo.js +169 -0
- package/modules/repositories/authorization-session-repository-postgres.js +170 -0
- package/modules/repositories/module-repository-documentdb.js +89 -41
- package/modules/repositories/module-repository-factory.js +4 -6
- package/modules/repositories/module-repository-interface.js +13 -0
- package/modules/repositories/module-repository-mongo.js +40 -5
- package/modules/repositories/module-repository-postgres.js +42 -5
- package/modules/repositories/module-repository.js +28 -0
- package/modules/requester/api-key.js +0 -2
- package/modules/requester/basic.js +0 -1
- package/modules/requester/oauth-2.js +3 -14
- package/modules/test/mock-api/definition.js +15 -10
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +10 -4
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/delete-module-entity.js +23 -0
- package/modules/use-cases/get-authorization-requirements.js +110 -0
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +8 -6
- package/modules/use-cases/get-module-entity-by-id.js +16 -0
- package/modules/use-cases/get-module.js +12 -9
- package/modules/use-cases/process-authorization-callback.js +12 -6
- package/modules/use-cases/process-authorization-step.js +160 -0
- package/modules/use-cases/refresh-entity-options.js +8 -6
- package/modules/use-cases/start-authorization-session.js +86 -0
- package/modules/use-cases/test-module-auth.js +8 -6
- package/modules/use-cases/update-module-entity.js +27 -0
- package/modules/utils/map-module-dto.js +2 -2
- package/openapi/openapi-spec-generator.js +370 -0
- package/openapi/openapi-v1.yaml +1360 -0
- package/openapi/openapi-v2.yaml +1535 -0
- package/package.json +7 -9
- package/prisma-mongodb/schema.prisma +89 -2
- package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
- package/prisma-postgresql/schema.prisma +87 -2
- package/providers/index.js +13 -0
- package/providers/resolve-provider.js +93 -0
- package/queues/index.js +19 -0
- package/queues/providers/index.js +7 -0
- package/queues/providers/netlify-background-provider.js +76 -0
- package/queues/providers/qstash-queue-provider.js +122 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-provider-factory.js +93 -0
- package/queues/queue-provider.js +46 -0
- package/queues/queuer-util.js +33 -25
- package/syncs/repositories/sync-repository-documentdb.js +39 -19
- package/syncs/repositories/sync-repository-factory.js +4 -4
- package/syncs/repositories/sync-repository-interface.js +6 -2
- package/syncs/repositories/sync-repository-postgres.js +19 -13
- package/syncs/sync.js +104 -100
- package/token/repositories/token-repository-factory.js +4 -6
- package/types/assertions/index.d.ts +72 -72
- package/types/core/index.d.ts +98 -53
- package/types/errors/index.d.ts +61 -54
- package/types/eslint-config/index.d.ts +40 -40
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/prettier-config/index.d.ts +5 -5
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +95 -93
- package/user/repositories/user-repository-factory.js +4 -4
- package/user/tests/doubles/test-user-repository.js +5 -2
- package/user/use-cases/authenticate-user.js +2 -4
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +8 -7
- package/user/use-cases/create-organization-user.js +1 -1
- package/user/use-cases/create-token-for-user-id.js +1 -1
- package/user/use-cases/delete-user.js +47 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
- package/user/use-cases/get-user-from-bearer-token.js +7 -3
- package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +18 -4
- package/utils/backend-path.js +2 -2
- package/utils/index.js +4 -1
- package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
- package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
- package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
- package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
- package/websocket/repositories/websocket-connection-repository.js +26 -20
- package/websocket/websocket-message-sender-interface.js +38 -0
- package/database/adapters/lambda-invoker.js +0 -97
- package/database/repositories/migration-status-repository-s3.js +0 -137
- package/generated/prisma-mongodb/runtime/binary.d.ts +0 -1
- package/generated/prisma-mongodb/runtime/binary.js +0 -289
- package/generated/prisma-postgresql/runtime/binary.d.ts +0 -1
- package/generated/prisma-postgresql/runtime/binary.js +0 -289
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
package/handlers/WEBHOOKS.md
CHANGED
|
@@ -5,31 +5,36 @@ This document explains how to implement webhook handling for your Frigg integrat
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
7
|
Frigg provides a scalable webhook architecture that:
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
8
|
+
|
|
9
|
+
- **Receives webhooks without database connections** for fast response times
|
|
10
|
+
- **Queues webhooks to SQS** for async processing
|
|
11
|
+
- **Processes webhooks with fully hydrated integrations** (with DB and API modules loaded)
|
|
12
|
+
- **Supports custom signature verification** for security
|
|
13
|
+
- **Throttles database connections** using SQS to handle webhook bursts
|
|
13
14
|
|
|
14
15
|
## Architecture
|
|
15
16
|
|
|
16
17
|
The webhook flow consists of two stages:
|
|
17
18
|
|
|
18
19
|
### Stage 1: HTTP Webhook Receiver (No DB)
|
|
20
|
+
|
|
19
21
|
```
|
|
20
22
|
Webhook → Lambda → WEBHOOK_RECEIVED event → Queue to SQS → 200 OK Response
|
|
21
23
|
```
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
24
|
+
|
|
25
|
+
- Fast response (no database query)
|
|
26
|
+
- Optional signature verification
|
|
27
|
+
- Messages queued for processing
|
|
25
28
|
|
|
26
29
|
### Stage 2: Queue Worker (DB-Connected)
|
|
30
|
+
|
|
27
31
|
```
|
|
28
32
|
SQS Queue → Lambda Worker → ON_WEBHOOK event → Process with hydrated integration
|
|
29
33
|
```
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
34
|
+
|
|
35
|
+
- Full database access
|
|
36
|
+
- API modules loaded
|
|
37
|
+
- Can use integration context
|
|
33
38
|
|
|
34
39
|
## Enabling Webhooks
|
|
35
40
|
|
|
@@ -59,7 +64,9 @@ class MyIntegration extends IntegrationBase {
|
|
|
59
64
|
static Definition = {
|
|
60
65
|
name: 'my-integration',
|
|
61
66
|
version: '1.0.0',
|
|
62
|
-
modules: {
|
|
67
|
+
modules: {
|
|
68
|
+
/* ... */
|
|
69
|
+
},
|
|
63
70
|
webhooks: {
|
|
64
71
|
enabled: true,
|
|
65
72
|
// Future options will be added here
|
|
@@ -73,20 +80,24 @@ class MyIntegration extends IntegrationBase {
|
|
|
73
80
|
When webhooks are enabled, two routes are automatically created:
|
|
74
81
|
|
|
75
82
|
### General Webhook
|
|
83
|
+
|
|
76
84
|
```
|
|
77
85
|
POST /api/{integrationName}-integration/webhooks
|
|
78
86
|
```
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
87
|
+
|
|
88
|
+
- No integration ID required
|
|
89
|
+
- Useful for system-wide events
|
|
90
|
+
- Creates unhydrated integration instance
|
|
82
91
|
|
|
83
92
|
### Integration-Specific Webhook
|
|
93
|
+
|
|
84
94
|
```
|
|
85
95
|
POST /api/{integrationName}-integration/webhooks/:integrationId
|
|
86
96
|
```
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
97
|
+
|
|
98
|
+
- Includes integration ID in URL
|
|
99
|
+
- Worker loads full integration with DB and modules
|
|
100
|
+
- Recommended for most use cases
|
|
90
101
|
|
|
91
102
|
## Event Handlers
|
|
92
103
|
|
|
@@ -95,6 +106,7 @@ POST /api/{integrationName}-integration/webhooks/:integrationId
|
|
|
95
106
|
Triggered when a webhook HTTP request is received (no database connection).
|
|
96
107
|
|
|
97
108
|
#### Default Behavior
|
|
109
|
+
|
|
98
110
|
Queues the webhook to SQS and responds with `200 OK`:
|
|
99
111
|
|
|
100
112
|
```javascript
|
|
@@ -125,7 +137,7 @@ class MyIntegration extends IntegrationBase {
|
|
|
125
137
|
// Verify webhook signature
|
|
126
138
|
const signature = req.headers['x-webhook-signature'];
|
|
127
139
|
const expectedSignature = this.calculateSignature(req.body);
|
|
128
|
-
|
|
140
|
+
|
|
129
141
|
if (signature !== expectedSignature) {
|
|
130
142
|
return res.status(401).json({ error: 'Invalid signature' });
|
|
131
143
|
}
|
|
@@ -156,6 +168,7 @@ class MyIntegration extends IntegrationBase {
|
|
|
156
168
|
Triggered by the queue worker (with database connection and hydrated integration).
|
|
157
169
|
|
|
158
170
|
#### Default Behavior
|
|
171
|
+
|
|
159
172
|
Logs the webhook data (override this!):
|
|
160
173
|
|
|
161
174
|
```javascript
|
|
@@ -262,27 +275,26 @@ class SlackIntegration extends IntegrationBase {
|
|
|
262
275
|
const crypto = require('crypto');
|
|
263
276
|
const signingSecret = process.env.SLACK_SIGNING_SECRET;
|
|
264
277
|
const timestamp = req.headers['x-slack-request-timestamp'];
|
|
265
|
-
|
|
278
|
+
|
|
266
279
|
// Validate timestamp is recent (within 5 minutes)
|
|
267
280
|
const currentTime = Math.floor(Date.now() / 1000);
|
|
268
281
|
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
|
|
269
282
|
return false; // Request is older than 5 minutes
|
|
270
283
|
}
|
|
271
|
-
|
|
284
|
+
|
|
272
285
|
const hmac = crypto.createHmac('sha256', signingSecret);
|
|
273
286
|
hmac.update(`v0:${timestamp}:${JSON.stringify(req.body)}`);
|
|
274
287
|
const expected = `v0=${hmac.digest('hex')}`;
|
|
275
|
-
|
|
288
|
+
|
|
276
289
|
// Check lengths first to avoid errors in timingSafeEqual
|
|
277
|
-
const expectedBuffer = Buffer.from(expected)
|
|
278
|
-
const signatureBuffer = Buffer.from(signature)
|
|
279
|
-
|
|
290
|
+
const expectedBuffer = Buffer.from(expected);
|
|
291
|
+
const signatureBuffer = Buffer.from(signature);
|
|
292
|
+
|
|
280
293
|
if (expectedBuffer.length !== signatureBuffer.length) {
|
|
281
|
-
return false
|
|
294
|
+
return false;
|
|
282
295
|
}
|
|
283
|
-
|
|
284
|
-
return crypto.timingSafeEqual(expectedBuffer, signatureBuffer)
|
|
285
296
|
|
|
297
|
+
return crypto.timingSafeEqual(expectedBuffer, signatureBuffer);
|
|
286
298
|
}
|
|
287
299
|
}
|
|
288
300
|
```
|
|
@@ -445,6 +457,7 @@ describe('MyIntegration Webhooks', () => {
|
|
|
445
457
|
## Best Practices
|
|
446
458
|
|
|
447
459
|
### 1. Always Verify Signatures
|
|
460
|
+
|
|
448
461
|
```javascript
|
|
449
462
|
async onWebhookReceived({ req, res }) {
|
|
450
463
|
// Verify before queueing
|
|
@@ -457,14 +470,17 @@ async onWebhookReceived({ req, res }) {
|
|
|
457
470
|
```
|
|
458
471
|
|
|
459
472
|
### 2. Respond Quickly
|
|
473
|
+
|
|
460
474
|
The `WEBHOOK_RECEIVED` handler should complete in < 3 seconds:
|
|
461
|
-
|
|
462
|
-
-
|
|
463
|
-
-
|
|
475
|
+
|
|
476
|
+
- Verify signature
|
|
477
|
+
- Queue message
|
|
478
|
+
- Return 200 OK
|
|
464
479
|
|
|
465
480
|
Heavy processing goes in `ON_WEBHOOK`.
|
|
466
481
|
|
|
467
482
|
### 3. Handle Idempotency
|
|
483
|
+
|
|
468
484
|
```javascript
|
|
469
485
|
async onWebhook({ data }) {
|
|
470
486
|
const { body } = data;
|
|
@@ -484,6 +500,7 @@ async onWebhook({ data }) {
|
|
|
484
500
|
```
|
|
485
501
|
|
|
486
502
|
### 4. Error Handling
|
|
503
|
+
|
|
487
504
|
```javascript
|
|
488
505
|
async onWebhook({ data }) {
|
|
489
506
|
try {
|
|
@@ -491,7 +508,7 @@ async onWebhook({ data }) {
|
|
|
491
508
|
} catch (error) {
|
|
492
509
|
// Log error - message will go to DLQ after retries
|
|
493
510
|
console.error('Webhook processing failed:', error);
|
|
494
|
-
|
|
511
|
+
|
|
495
512
|
// Update integration status if needed
|
|
496
513
|
await this.updateIntegrationMessages.execute(
|
|
497
514
|
this.id,
|
|
@@ -500,7 +517,7 @@ async onWebhook({ data }) {
|
|
|
500
517
|
error.message,
|
|
501
518
|
Date.now()
|
|
502
519
|
);
|
|
503
|
-
|
|
520
|
+
|
|
504
521
|
throw error; // Re-throw for retry/DLQ
|
|
505
522
|
}
|
|
506
523
|
}
|
|
@@ -513,18 +530,20 @@ async onWebhook({ data }) {
|
|
|
513
530
|
When `webhooks: true` is set, the Frigg infrastructure automatically creates:
|
|
514
531
|
|
|
515
532
|
1. **HTTP Lambda Function**
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
533
|
+
|
|
534
|
+
- Handler: `integration-webhook-routers.js`
|
|
535
|
+
- No database connection
|
|
536
|
+
- Fast cold start
|
|
519
537
|
|
|
520
538
|
2. **Webhook Routes**
|
|
521
|
-
- `POST /api/{name}-integration/webhooks`
|
|
522
|
-
- `POST /api/{name}-integration/webhooks/:integrationId`
|
|
523
539
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
540
|
+
- `POST /api/{name}-integration/webhooks`
|
|
541
|
+
- `POST /api/{name}-integration/webhooks/:integrationId`
|
|
542
|
+
|
|
543
|
+
3. **Queue Worker**
|
|
544
|
+
- Processes from existing integration queue
|
|
545
|
+
- Handles `ON_WEBHOOK` events
|
|
546
|
+
- Full database access
|
|
528
547
|
|
|
529
548
|
### Serverless Configuration (Automatic)
|
|
530
549
|
|
|
@@ -532,22 +551,22 @@ The following is generated automatically in `serverless.yml`:
|
|
|
532
551
|
|
|
533
552
|
```yaml
|
|
534
553
|
functions:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
554
|
+
myintegrationWebhook:
|
|
555
|
+
handler: node_modules/@friggframework/core/handlers/routers/integration-webhook-routers.handlers.myintegrationWebhook.handler
|
|
556
|
+
events:
|
|
557
|
+
- httpApi:
|
|
558
|
+
path: /api/myintegration-integration/webhooks
|
|
559
|
+
method: POST
|
|
560
|
+
- httpApi:
|
|
561
|
+
path: /api/myintegration-integration/webhooks/{integrationId}
|
|
562
|
+
method: POST
|
|
563
|
+
|
|
564
|
+
myintegrationQueueWorker:
|
|
565
|
+
handler: node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.myintegration.queueWorker
|
|
566
|
+
events:
|
|
567
|
+
- sqs:
|
|
568
|
+
arn: !GetAtt MyintegrationQueue.Arn
|
|
569
|
+
batchSize: 1
|
|
551
570
|
```
|
|
552
571
|
|
|
553
572
|
## Event Handler Reference
|
|
@@ -560,13 +579,14 @@ functions:
|
|
|
560
579
|
**Must:** Respond to `res` with status code
|
|
561
580
|
|
|
562
581
|
**Parameters:**
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
-
|
|
569
|
-
|
|
582
|
+
|
|
583
|
+
- `req` - Express request object
|
|
584
|
+
- `req.body` - Webhook payload
|
|
585
|
+
- `req.params.integrationId` - Integration ID (if in URL)
|
|
586
|
+
- `req.headers` - HTTP headers
|
|
587
|
+
- `req.query` - Query parameters
|
|
588
|
+
- `res` - Express response object
|
|
589
|
+
- Call `res.status(code).json(data)` to respond
|
|
570
590
|
|
|
571
591
|
### onWebhook({ data, context })
|
|
572
592
|
|
|
@@ -576,12 +596,13 @@ functions:
|
|
|
576
596
|
**Can:** Use `this.modules`, `this.config`, DB operations
|
|
577
597
|
|
|
578
598
|
**Parameters:**
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
-
|
|
599
|
+
|
|
600
|
+
- `data` - Queued webhook data
|
|
601
|
+
- `data.integrationId` - Integration ID (if provided)
|
|
602
|
+
- `data.body` - Original webhook payload
|
|
603
|
+
- `data.headers` - Original HTTP headers
|
|
604
|
+
- `data.query` - Original query parameters
|
|
605
|
+
- `context` - Lambda context object
|
|
585
606
|
|
|
586
607
|
## Queue Helper
|
|
587
608
|
|
|
@@ -608,6 +629,7 @@ Automatically uses the correct SQS queue URL based on integration name.
|
|
|
608
629
|
**Error:** `Queue URL not found for {NAME}_QUEUE_URL`
|
|
609
630
|
|
|
610
631
|
**Solution:** Ensure environment variable is set:
|
|
632
|
+
|
|
611
633
|
```bash
|
|
612
634
|
export MY_INTEGRATION_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/...
|
|
613
635
|
```
|
|
@@ -615,6 +637,7 @@ export MY_INTEGRATION_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/...
|
|
|
615
637
|
### Webhook Not Responding
|
|
616
638
|
|
|
617
639
|
**Check:**
|
|
640
|
+
|
|
618
641
|
1. Is `webhooks: true` in Definition?
|
|
619
642
|
2. Is webhook endpoint deployed?
|
|
620
643
|
3. Are you sending POST requests?
|
|
@@ -623,6 +646,7 @@ export MY_INTEGRATION_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/...
|
|
|
623
646
|
### Worker Not Processing
|
|
624
647
|
|
|
625
648
|
**Check:**
|
|
649
|
+
|
|
626
650
|
1. Is SQS queue receiving messages?
|
|
627
651
|
2. Is queue worker Lambda function deployed?
|
|
628
652
|
3. Check CloudWatch logs for worker errors
|
|
@@ -638,16 +662,15 @@ export MY_INTEGRATION_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/...
|
|
|
638
662
|
|
|
639
663
|
## Performance
|
|
640
664
|
|
|
641
|
-
-
|
|
642
|
-
-
|
|
643
|
-
-
|
|
644
|
-
-
|
|
665
|
+
- **HTTP Response:** < 100ms (signature check + queue)
|
|
666
|
+
- **Worker Processing:** Based on your logic
|
|
667
|
+
- **Concurrency:** Controlled by SQS worker `reservedConcurrency: 5`
|
|
668
|
+
- **Burst Handling:** Unlimited HTTP, throttled processing
|
|
645
669
|
|
|
646
670
|
## Related Files
|
|
647
671
|
|
|
648
|
-
-
|
|
649
|
-
-
|
|
650
|
-
-
|
|
651
|
-
-
|
|
652
|
-
-
|
|
653
|
-
|
|
672
|
+
- `packages/core/integrations/integration-base.js` - Event definitions and default handlers
|
|
673
|
+
- `packages/core/handlers/routers/integration-webhook-routers.js` - HTTP webhook routes
|
|
674
|
+
- `packages/core/handlers/backend-utils.js` - Queue worker with hydration logic
|
|
675
|
+
- `packages/core/handlers/integration-event-dispatcher.js` - Event dispatching
|
|
676
|
+
- `packages/devtools/infrastructure/serverless-template.js` - Automatic infrastructure generation
|
|
@@ -3,18 +3,60 @@ const path = require('node:path');
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Cached app definition, set via setAppDefinition().
|
|
7
|
+
* When set, loadAppDefinition() returns this instead of discovering
|
|
8
|
+
* the backend via process.cwd().
|
|
9
|
+
*/
|
|
10
|
+
let cachedAppDefinition = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pre-set the app definition so loadAppDefinition() can return it
|
|
14
|
+
* without needing process.cwd()-based discovery.
|
|
15
|
+
*
|
|
16
|
+
* This is critical for platforms like Netlify where:
|
|
17
|
+
* - nft/esbuild traces static require() calls to determine bundle contents
|
|
18
|
+
* - process.cwd() at runtime points to /var/task/, not the backend directory
|
|
19
|
+
* - The caller can use a static require('../../backend/index.js') to make
|
|
20
|
+
* the backend traceable, then pass the definition here
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} definition - The app definition object (backend's Definition export)
|
|
23
|
+
*/
|
|
24
|
+
function setAppDefinition(definition) {
|
|
25
|
+
cachedAppDefinition = definition;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Loads the App definition from the nearest backend package.
|
|
30
|
+
*
|
|
31
|
+
* If setAppDefinition() was called, returns the cached definition
|
|
32
|
+
* immediately (no filesystem discovery needed).
|
|
33
|
+
*
|
|
34
|
+
* Otherwise, discovers the backend via process.cwd() traversal.
|
|
35
|
+
*
|
|
36
|
+
* Returns the full appDefinition object plus convenience destructured fields
|
|
37
|
+
* for backward compatibility (integrations, userConfig).
|
|
38
|
+
*
|
|
7
39
|
* @function loadAppDefinition
|
|
8
|
-
* @
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
40
|
+
* @returns {{
|
|
41
|
+
* integrations: Array<object>,
|
|
42
|
+
* userConfig: object | null,
|
|
43
|
+
* appDefinition: object
|
|
44
|
+
* }}
|
|
45
|
+
* @throws {Error} If backend package.json or index.js cannot be found.
|
|
13
46
|
* @example
|
|
14
|
-
*
|
|
15
|
-
*
|
|
47
|
+
* // Existing callers still work:
|
|
48
|
+
* const { integrations, userConfig } = loadAppDefinition();
|
|
49
|
+
*
|
|
50
|
+
* // New callers can access the full definition:
|
|
51
|
+
* const { appDefinition } = loadAppDefinition();
|
|
52
|
+
* console.log(appDefinition.provider); // 'aws' | 'netlify'
|
|
16
53
|
*/
|
|
17
54
|
function loadAppDefinition() {
|
|
55
|
+
if (cachedAppDefinition) {
|
|
56
|
+
const { integrations = [], user: userConfig = null } = cachedAppDefinition;
|
|
57
|
+
return { integrations, userConfig, appDefinition: cachedAppDefinition };
|
|
58
|
+
}
|
|
59
|
+
|
|
18
60
|
const backendPath = findNearestBackendPackageJson();
|
|
19
61
|
if (!backendPath) {
|
|
20
62
|
throw new Error('Could not find backend package.json');
|
|
@@ -30,9 +72,10 @@ function loadAppDefinition() {
|
|
|
30
72
|
const appDefinition = backendJsFile.Definition;
|
|
31
73
|
|
|
32
74
|
const { integrations = [], user: userConfig = null } = appDefinition;
|
|
33
|
-
return { integrations, userConfig };
|
|
75
|
+
return { integrations, userConfig, appDefinition };
|
|
34
76
|
}
|
|
35
77
|
|
|
36
78
|
module.exports = {
|
|
37
79
|
loadAppDefinition,
|
|
38
|
-
|
|
80
|
+
setAppDefinition,
|
|
81
|
+
};
|
|
@@ -3,6 +3,7 @@ const express = require('express');
|
|
|
3
3
|
const bodyParser = require('body-parser');
|
|
4
4
|
const cors = require('cors');
|
|
5
5
|
const Boom = require('@hapi/boom');
|
|
6
|
+
const loadUserManager = require('./routers/middleware/loadUser');
|
|
6
7
|
const serverlessHttp = require('serverless-http');
|
|
7
8
|
|
|
8
9
|
const createApp = (applyMiddleware) => {
|
|
@@ -19,6 +20,8 @@ const createApp = (applyMiddleware) => {
|
|
|
19
20
|
})
|
|
20
21
|
);
|
|
21
22
|
|
|
23
|
+
app.use(loadUserManager);
|
|
24
|
+
|
|
22
25
|
if (applyMiddleware) applyMiddleware(app);
|
|
23
26
|
|
|
24
27
|
// Handle sending error response and logging server errors to console
|
|
@@ -32,7 +35,6 @@ const createApp = (applyMiddleware) => {
|
|
|
32
35
|
flushDebugLog(boomError);
|
|
33
36
|
res.status(statusCode).json({ error: 'Internal Server Error' });
|
|
34
37
|
} else {
|
|
35
|
-
console.warn(`[Frigg] ${req.method} ${req.path} -> ${statusCode}: ${err.message}`);
|
|
36
38
|
res.status(statusCode).json({ error: err.message });
|
|
37
39
|
}
|
|
38
40
|
});
|
|
@@ -40,9 +42,18 @@ const createApp = (applyMiddleware) => {
|
|
|
40
42
|
return app;
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
function createAppHandler(
|
|
45
|
+
function createAppHandler(
|
|
46
|
+
eventName,
|
|
47
|
+
router,
|
|
48
|
+
shouldUseDatabase = true,
|
|
49
|
+
basePath = null
|
|
50
|
+
) {
|
|
44
51
|
const app = createApp((app) => {
|
|
45
|
-
|
|
52
|
+
if (basePath) {
|
|
53
|
+
app.use(basePath, router);
|
|
54
|
+
} else {
|
|
55
|
+
app.use(router);
|
|
56
|
+
}
|
|
46
57
|
});
|
|
47
58
|
return createHandler({
|
|
48
59
|
eventName,
|
|
@@ -88,16 +88,9 @@ const loadIntegrationForWebhook = async (integrationId) => {
|
|
|
88
88
|
moduleFactory,
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
await integrationRepository.findIntegrationById(integrationId);
|
|
95
|
-
} catch (error) {
|
|
96
|
-
if (error.message?.includes('not found')) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
throw error;
|
|
100
|
-
}
|
|
91
|
+
const integrationRecord = await integrationRepository.findIntegrationById(
|
|
92
|
+
integrationId
|
|
93
|
+
);
|
|
101
94
|
|
|
102
95
|
const instance = await getIntegrationInstance.execute(
|
|
103
96
|
integrationId,
|
|
@@ -108,7 +101,6 @@ const loadIntegrationForWebhook = async (integrationId) => {
|
|
|
108
101
|
};
|
|
109
102
|
|
|
110
103
|
const loadIntegrationForProcess = async (processId, integrationClass) => {
|
|
111
|
-
|
|
112
104
|
const { processRepository, integrationRepository, moduleRepository } =
|
|
113
105
|
initializeRepositories();
|
|
114
106
|
|
|
@@ -158,12 +150,6 @@ const createQueueWorker = (integrationClass) => {
|
|
|
158
150
|
integrationInstance = await loadIntegrationForWebhook(
|
|
159
151
|
params.data.integrationId
|
|
160
152
|
);
|
|
161
|
-
if (!integrationInstance) {
|
|
162
|
-
console.warn(
|
|
163
|
-
`[${integrationClass.Definition.name}] Integration ${params.data.integrationId} no longer exists. Discarding ${params.event} message.`
|
|
164
|
-
);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
153
|
} else {
|
|
168
154
|
// Instantiates a DRY integration class without database records.
|
|
169
155
|
// There will be cases where we need to use helpers that the api modules can export.
|