@forumone/throughline-integrations 0.0.1 → 0.2.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 +7 -0
- package/LICENSE +21 -0
- package/README.md +94 -28
- package/dist/collection.d.ts +16 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +119 -0
- package/dist/collection.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/index.d.ts +3 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +2 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/webhook/config-fields.d.ts +33 -0
- package/dist/integrations/webhook/config-fields.d.ts.map +1 -0
- package/dist/integrations/webhook/config-fields.js +54 -0
- package/dist/integrations/webhook/config-fields.js.map +1 -0
- package/dist/integrations/webhook/functions.d.ts +21 -0
- package/dist/integrations/webhook/functions.d.ts.map +1 -0
- package/dist/integrations/webhook/functions.js +124 -0
- package/dist/integrations/webhook/functions.js.map +1 -0
- package/dist/integrations/webhook/healthcheck.d.ts +10 -0
- package/dist/integrations/webhook/healthcheck.d.ts.map +1 -0
- package/dist/integrations/webhook/healthcheck.js +26 -0
- package/dist/integrations/webhook/healthcheck.js.map +1 -0
- package/dist/integrations/webhook/hmac.d.ts +11 -0
- package/dist/integrations/webhook/hmac.d.ts.map +1 -0
- package/dist/integrations/webhook/hmac.js +25 -0
- package/dist/integrations/webhook/hmac.js.map +1 -0
- package/dist/integrations/webhook/index.d.ts +6 -0
- package/dist/integrations/webhook/index.d.ts.map +1 -0
- package/dist/integrations/webhook/index.js +46 -0
- package/dist/integrations/webhook/index.js.map +1 -0
- package/dist/integrations/webhook/payload.d.ts +9 -0
- package/dist/integrations/webhook/payload.d.ts.map +1 -0
- package/dist/integrations/webhook/payload.js +19 -0
- package/dist/integrations/webhook/payload.js.map +1 -0
- package/dist/options.d.ts +22 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +8 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +19 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +162 -0
- package/dist/plugin.js.map +1 -0
- package/dist/registry.d.ts +15 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +27 -0
- package/dist/registry.js.map +1 -0
- package/dist/tools/access.d.ts +7 -0
- package/dist/tools/access.d.ts.map +1 -0
- package/dist/tools/access.js +15 -0
- package/dist/tools/access.js.map +1 -0
- package/dist/tools/get-integration-status.d.ts +17 -0
- package/dist/tools/get-integration-status.d.ts.map +1 -0
- package/dist/tools/get-integration-status.js +41 -0
- package/dist/tools/get-integration-status.js.map +1 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-integration-types.d.ts +10 -0
- package/dist/tools/list-integration-types.d.ts.map +1 -0
- package/dist/tools/list-integration-types.js +22 -0
- package/dist/tools/list-integration-types.js.map +1 -0
- package/dist/tools/list-integrations.d.ts +20 -0
- package/dist/tools/list-integrations.d.ts.map +1 -0
- package/dist/tools/list-integrations.js +49 -0
- package/dist/tools/list-integrations.js.map +1 -0
- package/dist/tools/test-integration.d.ts +19 -0
- package/dist/tools/test-integration.d.ts.map +1 -0
- package/dist/tools/test-integration.js +50 -0
- package/dist/tools/test-integration.js.map +1 -0
- package/dist/tools/trigger-sync.d.ts +22 -0
- package/dist/tools/trigger-sync.d.ts.map +1 -0
- package/dist/tools/trigger-sync.js +60 -0
- package/dist/tools/trigger-sync.js.map +1 -0
- package/dist/types.d.ts +116 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @forumone/throughline-integrations
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 789dbd8: Initial release of the integrations server. Provides the `Integration` plugin contract every future integration (Salesforce, Mailchimp, etc.) follows, the registry, the admin-only Integrations collection, and five MCP tools (`list_integrations`, `get_integration_status`, `trigger_sync`, `test_integration`, `list_integration_types`). Includes a generic outbound webhook integration with HMAC-SHA256 signing (RFC 4231 known-answer tests pinned), configurable event filter / payload mode / timeout, healthcheck, and Inngest-driven retries. Configuration is admin-only by design — Claude can trigger and observe integrations but cannot retarget URLs or rotate secrets.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Forum One Communications Corporation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,45 +1,111 @@
|
|
|
1
1
|
# @forumone/throughline-integrations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Plugin architecture for connecting Throughline-powered Payload sites to external systems. Ships the `Integration` contract every future integration follows, the registry, the per-instance configuration collection, five MCP tools, and a generic outbound webhook integration as the reference implementation.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## What this package provides
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **`Integration` contract** — id, name, description, configFields, validateConfig, subscribes, createFunctions, mcpTools (optional), healthcheck. Every Salesforce / Mailchimp / Slack / etc. integration uses this exact shape.
|
|
8
|
+
- **`IntegrationRegistry`** — process-local, per-plugin-init store keyed by integration id. Rejects duplicates synchronously.
|
|
9
|
+
- **Integrations collection** — `name`, `integrationType`, `enabled`, `config` (json), and read-only `lastSyncAt` / `lastSyncStatus` / `lastError`. Admin-only writes; admin/editor reads.
|
|
10
|
+
- **Five MCP tools** at `/api/integrations/mcp`:
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
| Tool | Use it for | Access |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `list_integrations` | "What integrations are configured?" / "Which are healthy?" | admin / editor |
|
|
15
|
+
| `get_integration_status` | One instance's last-sync info | admin / editor |
|
|
16
|
+
| `trigger_sync` | Manual delivery to verify connectivity after a config change | admin only |
|
|
17
|
+
| `test_integration` | Run the integration's healthcheck (no event emitted) | admin / editor |
|
|
18
|
+
| `list_integration_types` | "What kinds of integrations are supported here?" | any caller |
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@forumone/throughline-integrations`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
20
|
+
- **Webhook integration** — generic outbound HTTPS POST with HMAC-SHA256 signing, configurable event filter, retries (5x), timeout, and a HEAD-based healthcheck. RFC 4231 known-answer test vectors pin the wire format so refactoring can never silently break receivers.
|
|
15
21
|
|
|
16
|
-
##
|
|
22
|
+
## Why this is separate from the other server packages
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
The other server packages do one job well. This is a **framework within the framework**: a contract for integration modules plus tooling to register, configure, observe, and trigger them. Every real client engagement needs integrations; building them ad-hoc produces unmaintainable tangle. This package keeps the surface area bounded as the integration count grows.
|
|
19
25
|
|
|
20
|
-
##
|
|
26
|
+
## Installation
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add @forumone/throughline-integrations
|
|
30
|
+
```
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
32
|
+
Peers: `payload@^3.0.0`, `inngest@^4.0.0`. Required runtime peer: `@forumone/throughline-core` (audit log).
|
|
28
33
|
|
|
29
|
-
##
|
|
34
|
+
## Usage
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- Exists only for administrative purposes
|
|
36
|
+
```ts
|
|
37
|
+
import { buildConfig } from 'payload'
|
|
38
|
+
import { auditPlugin, createInngestClient } from '@forumone/throughline-core'
|
|
39
|
+
import { integrationsPlugin } from '@forumone/throughline-integrations'
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
const inngest = createInngestClient({ id: 'my-site' })
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
export default buildConfig({
|
|
44
|
+
// collections, db, secret...
|
|
45
|
+
plugins: [
|
|
46
|
+
auditPlugin({ inngest }),
|
|
47
|
+
integrationsPlugin({ inngest }),
|
|
48
|
+
],
|
|
49
|
+
})
|
|
50
|
+
```
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
## Adding integrations
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
```ts
|
|
55
|
+
import { integrationsPlugin } from '@forumone/throughline-integrations'
|
|
56
|
+
import { salesforceIntegration } from '@your-org/throughline-salesforce'
|
|
57
|
+
|
|
58
|
+
integrationsPlugin({
|
|
59
|
+
inngest,
|
|
60
|
+
integrations: [salesforceIntegration],
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The webhook integration is registered automatically. Additional integrations are appended; duplicate ids throw at plugin init.
|
|
65
|
+
|
|
66
|
+
## Wiring Inngest functions
|
|
67
|
+
|
|
68
|
+
Integration `createFunctions` returns Inngest functions, but **this plugin does not serve them**. The client app's Inngest endpoint composes them with its own functions. See [`docs/integrations-wiring.md`](../../docs/integrations-wiring.md) in the repository root for the pattern.
|
|
69
|
+
|
|
70
|
+
## Why configuration is admin-only
|
|
71
|
+
|
|
72
|
+
A prompt-injection attacker could otherwise convince Claude to retarget a webhook to attacker-controlled infrastructure or rotate the signing secret. Claude can _trigger_ and _observe_ integrations conversationally, but configuration changes are deliberate human actions in the Payload admin.
|
|
73
|
+
|
|
74
|
+
This asymmetry is intentional and is why `trigger_sync` is admin-only too — manual triggering writes to an external system, even if it doesn't change configuration.
|
|
75
|
+
|
|
76
|
+
## Webhook details
|
|
77
|
+
|
|
78
|
+
| Field | Behaviour |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `targetUrl` | Required, must be `https://`. Validated at write time. |
|
|
81
|
+
| `signingSecret` | Required, ≥ 32 characters. Used as the HMAC-SHA256 key. |
|
|
82
|
+
| `eventFilter` | Optional list of event names; empty list = deliver all subscribed events. |
|
|
83
|
+
| `includeFullPayload` | If false (default), only `id`, `slug`, and `*Id` fields go in the body. |
|
|
84
|
+
| `timeoutSeconds` | Per-request timeout. Default 10. |
|
|
85
|
+
|
|
86
|
+
Outbound headers:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
content-type: application/json
|
|
90
|
+
x-throughline-event: <event name>
|
|
91
|
+
x-throughline-signature: sha256=<hex digest>
|
|
92
|
+
x-throughline-timestamp: <epoch ms>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The HMAC is computed over the entire request body (a JSON-stringified envelope of `{ event, data, timestamp, instanceId }`). Receivers verify by recomputing with the shared secret and comparing in constant time.
|
|
96
|
+
|
|
97
|
+
## Options
|
|
98
|
+
|
|
99
|
+
| Option | Type | Default | Notes |
|
|
100
|
+
|---|---|---|---|
|
|
101
|
+
| `inngest` | `Inngest` | required | Throws at validate if missing |
|
|
102
|
+
| `integrations` | `Integration[]` | `[]` | Appended to the built-in webhook integration |
|
|
103
|
+
| `collectionSlug` | `string` | `'integrations'` | |
|
|
104
|
+
| `routePrefix` | `string` | `'/integrations'` | Payload prepends `/api`, so MCP lands at `/api/integrations/mcp` |
|
|
105
|
+
| `enabled` | `boolean` | `true` | Set to false to no-op |
|
|
106
|
+
| `logger` | `Logger` | `defaultLogger` | |
|
|
107
|
+
|
|
108
|
+
## Related packages
|
|
109
|
+
|
|
110
|
+
- `@forumone/throughline-core` — required peer; provides audit log and MCP handler
|
|
111
|
+
- `@forumone/throughline-publishing`, `@forumone/throughline-approvals`, `@forumone/throughline-audit` — emit events this package's webhook can deliver
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload';
|
|
2
|
+
import type { IntegrationRegistry } from './registry.js';
|
|
3
|
+
export interface CreateIntegrationsCollectionOptions {
|
|
4
|
+
slug?: string;
|
|
5
|
+
registry: IntegrationRegistry;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Collection that persists per-instance integration configuration. Configuration
|
|
9
|
+
* is admin-only by design: prompt injection that gave Claude write access here
|
|
10
|
+
* would let an attacker re-target webhook URLs or rotate signing secrets.
|
|
11
|
+
*
|
|
12
|
+
* Editors can read instance status (used by the listing/status MCP tools);
|
|
13
|
+
* write operations are reserved for admins editing in the Payload UI.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createIntegrationsCollection(options: CreateIntegrationsCollectionOptions): CollectionConfig;
|
|
16
|
+
//# sourceMappingURL=collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,gBAAgB,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAGxD,MAAM,WAAW,mCAAmC;IAClD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,mBAAmB,CAAA;CAC9B;AAYD;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,mCAAmC,GAC3C,gBAAgB,CA4GlB"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { DEFAULT_INTEGRATIONS_SLUG } from './options.js';
|
|
2
|
+
const adminOrEditor = ({ req }) => {
|
|
3
|
+
const roles = req.user?.['roles'] ?? [];
|
|
4
|
+
return roles.includes('admin') || roles.includes('editor');
|
|
5
|
+
};
|
|
6
|
+
const adminOnly = ({ req }) => {
|
|
7
|
+
const roles = req.user?.['roles'] ?? [];
|
|
8
|
+
return roles.includes('admin');
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Collection that persists per-instance integration configuration. Configuration
|
|
12
|
+
* is admin-only by design: prompt injection that gave Claude write access here
|
|
13
|
+
* would let an attacker re-target webhook URLs or rotate signing secrets.
|
|
14
|
+
*
|
|
15
|
+
* Editors can read instance status (used by the listing/status MCP tools);
|
|
16
|
+
* write operations are reserved for admins editing in the Payload UI.
|
|
17
|
+
*/
|
|
18
|
+
export function createIntegrationsCollection(options) {
|
|
19
|
+
const slug = options.slug ?? DEFAULT_INTEGRATIONS_SLUG;
|
|
20
|
+
const { registry } = options;
|
|
21
|
+
return {
|
|
22
|
+
slug,
|
|
23
|
+
admin: {
|
|
24
|
+
useAsTitle: 'name',
|
|
25
|
+
defaultColumns: ['name', 'integrationType', 'enabled', 'lastSyncAt', 'lastSyncStatus'],
|
|
26
|
+
description: 'External-system connections. Edit only in the admin — Claude can trigger and observe but cannot configure.',
|
|
27
|
+
},
|
|
28
|
+
access: {
|
|
29
|
+
read: adminOrEditor,
|
|
30
|
+
create: adminOnly,
|
|
31
|
+
update: adminOnly,
|
|
32
|
+
delete: adminOnly,
|
|
33
|
+
},
|
|
34
|
+
fields: [
|
|
35
|
+
{
|
|
36
|
+
name: 'name',
|
|
37
|
+
type: 'text',
|
|
38
|
+
required: true,
|
|
39
|
+
admin: { description: 'Display name for this instance (e.g. "Mailchimp - Newsletter").' },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'integrationType',
|
|
43
|
+
type: 'select',
|
|
44
|
+
required: true,
|
|
45
|
+
options: registry
|
|
46
|
+
.list()
|
|
47
|
+
.map((integration) => ({ label: integration.name, value: integration.id })),
|
|
48
|
+
admin: { description: 'The integration plugin this instance uses.' },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'enabled',
|
|
52
|
+
type: 'checkbox',
|
|
53
|
+
defaultValue: false,
|
|
54
|
+
admin: {
|
|
55
|
+
description: 'Disabled integrations are skipped by event delivery and excluded from `list_integrations` unless onlyEnabled=false.',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'config',
|
|
60
|
+
type: 'json',
|
|
61
|
+
admin: {
|
|
62
|
+
description: 'Integration-specific configuration. The integration\'s configFields drive the admin UI; this JSON store carries the validated payload.',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'lastSyncAt',
|
|
67
|
+
type: 'date',
|
|
68
|
+
admin: { readOnly: true, position: 'sidebar' },
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'lastSyncStatus',
|
|
72
|
+
type: 'select',
|
|
73
|
+
admin: { readOnly: true, position: 'sidebar' },
|
|
74
|
+
options: [
|
|
75
|
+
{ label: 'Never run', value: 'never-run' },
|
|
76
|
+
{ label: 'Success', value: 'success' },
|
|
77
|
+
{ label: 'Partial', value: 'partial' },
|
|
78
|
+
{ label: 'Failed', value: 'failed' },
|
|
79
|
+
],
|
|
80
|
+
defaultValue: 'never-run',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'lastError',
|
|
84
|
+
type: 'textarea',
|
|
85
|
+
admin: { readOnly: true },
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
indexes: [
|
|
89
|
+
{ fields: ['integrationType', 'enabled'] },
|
|
90
|
+
{ fields: ['lastSyncStatus'] },
|
|
91
|
+
],
|
|
92
|
+
hooks: {
|
|
93
|
+
beforeChange: [
|
|
94
|
+
async ({ data, operation }) => {
|
|
95
|
+
if (operation !== 'create' && operation !== 'update')
|
|
96
|
+
return data;
|
|
97
|
+
const incoming = data;
|
|
98
|
+
const integrationType = incoming['integrationType'];
|
|
99
|
+
if (typeof integrationType !== 'string')
|
|
100
|
+
return data;
|
|
101
|
+
const integration = registry.get(integrationType);
|
|
102
|
+
if (!integration) {
|
|
103
|
+
throw new Error(`Unknown integration type "${integrationType}". Known types: ${registry
|
|
104
|
+
.list()
|
|
105
|
+
.map((i) => i.id)
|
|
106
|
+
.join(', ') || '(none registered)'}.`);
|
|
107
|
+
}
|
|
108
|
+
const config = (incoming['config'] ?? {});
|
|
109
|
+
const validation = await integration.validateConfig(config);
|
|
110
|
+
if (!validation.ok) {
|
|
111
|
+
throw new Error(`Invalid config for ${integration.name}: ${validation.reason ?? 'no reason given'}`);
|
|
112
|
+
}
|
|
113
|
+
return data;
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=collection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { integrationsPlugin, getIntegrationRegistry, getIntegrationContext, } from './plugin.js';
|
|
2
|
+
export { IntegrationRegistry } from './registry.js';
|
|
3
|
+
export { DEFAULT_INTEGRATIONS_SLUG } from './options.js';
|
|
4
|
+
export type { IntegrationsPluginOptions } from './options.js';
|
|
5
|
+
export { webhookIntegration, WEBHOOK_INTEGRATION_ID } from './integrations/index.js';
|
|
6
|
+
export type { WebhookConfig } from './integrations/index.js';
|
|
7
|
+
export type { Integration, IntegrationCategory, IntegrationContext, IntegrationAuditEvent, IntegrationConfigValidation, IntegrationHealth, IntegrationInstance, IntegrationInstanceLoaded, IntegrationSyncStatus, } from './types.js';
|
|
8
|
+
export { createListIntegrationsTool, createGetIntegrationStatusTool, createTriggerSyncTool, createTestIntegrationTool, createListIntegrationTypesTool, } from './tools/index.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AACxD,YAAY,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAE7D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAE5D,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,EACnB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,kBAAkB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { integrationsPlugin, getIntegrationRegistry, getIntegrationContext, } from './plugin.js';
|
|
2
|
+
export { IntegrationRegistry } from './registry.js';
|
|
3
|
+
export { DEFAULT_INTEGRATIONS_SLUG } from './options.js';
|
|
4
|
+
export { webhookIntegration, WEBHOOK_INTEGRATION_ID } from './integrations/index.js';
|
|
5
|
+
export { createListIntegrationsTool, createGetIntegrationStatusTool, createTriggerSyncTool, createTestIntegrationTool, createListIntegrationTypesTool, } from './tools/index.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAGxD,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAepF,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC/E,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Field } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* The event names the webhook integration can subscribe to. Kept in sync
|
|
4
|
+
* with the core `FrameworkEvents` taxonomy plus the approval/decided event
|
|
5
|
+
* augmented by the approvals package.
|
|
6
|
+
*/
|
|
7
|
+
export declare const WEBHOOK_EVENT_OPTIONS: readonly [{
|
|
8
|
+
readonly label: "Page published";
|
|
9
|
+
readonly value: "content/page.published";
|
|
10
|
+
}, {
|
|
11
|
+
readonly label: "Page unpublished";
|
|
12
|
+
readonly value: "content/page.unpublished";
|
|
13
|
+
}, {
|
|
14
|
+
readonly label: "Page rolled back";
|
|
15
|
+
readonly value: "content/page.rolled_back";
|
|
16
|
+
}, {
|
|
17
|
+
readonly label: "Form submission received";
|
|
18
|
+
readonly value: "form/submission.received";
|
|
19
|
+
}, {
|
|
20
|
+
readonly label: "Approval decided";
|
|
21
|
+
readonly value: "approval/decided";
|
|
22
|
+
}];
|
|
23
|
+
export declare const WEBHOOK_DEFAULT_TIMEOUT_SECONDS = 10;
|
|
24
|
+
export declare const MIN_SIGNING_SECRET_LENGTH = 32;
|
|
25
|
+
export declare const configFields: Field[];
|
|
26
|
+
export interface WebhookConfig {
|
|
27
|
+
targetUrl: string;
|
|
28
|
+
signingSecret: string;
|
|
29
|
+
eventFilter?: string[];
|
|
30
|
+
includeFullPayload?: boolean;
|
|
31
|
+
timeoutSeconds?: number;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=config-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-fields.d.ts","sourceRoot":"","sources":["../../../src/integrations/webhook/config-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;EAMxB,CAAA;AAEV,eAAO,MAAM,+BAA+B,KAAK,CAAA;AACjD,eAAO,MAAM,yBAAyB,KAAK,CAAA;AAE3C,eAAO,MAAM,YAAY,EAAE,KAAK,EAwC/B,CAAA;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The event names the webhook integration can subscribe to. Kept in sync
|
|
3
|
+
* with the core `FrameworkEvents` taxonomy plus the approval/decided event
|
|
4
|
+
* augmented by the approvals package.
|
|
5
|
+
*/
|
|
6
|
+
export const WEBHOOK_EVENT_OPTIONS = [
|
|
7
|
+
{ label: 'Page published', value: 'content/page.published' },
|
|
8
|
+
{ label: 'Page unpublished', value: 'content/page.unpublished' },
|
|
9
|
+
{ label: 'Page rolled back', value: 'content/page.rolled_back' },
|
|
10
|
+
{ label: 'Form submission received', value: 'form/submission.received' },
|
|
11
|
+
{ label: 'Approval decided', value: 'approval/decided' },
|
|
12
|
+
];
|
|
13
|
+
export const WEBHOOK_DEFAULT_TIMEOUT_SECONDS = 10;
|
|
14
|
+
export const MIN_SIGNING_SECRET_LENGTH = 32;
|
|
15
|
+
export const configFields = [
|
|
16
|
+
{
|
|
17
|
+
name: 'targetUrl',
|
|
18
|
+
type: 'text',
|
|
19
|
+
required: true,
|
|
20
|
+
admin: { description: 'HTTPS URL the integration POSTs events to.' },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'signingSecret',
|
|
24
|
+
type: 'text',
|
|
25
|
+
required: true,
|
|
26
|
+
admin: {
|
|
27
|
+
description: `Shared secret used to compute HMAC-SHA256 signatures (>=${MIN_SIGNING_SECRET_LENGTH} chars). The receiver verifies the x-throughline-signature header to confirm the request originated from this site.`,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'eventFilter',
|
|
32
|
+
type: 'select',
|
|
33
|
+
hasMany: true,
|
|
34
|
+
options: WEBHOOK_EVENT_OPTIONS.map((o) => ({ label: o.label, value: o.value })),
|
|
35
|
+
admin: {
|
|
36
|
+
description: 'Only deliver these event types. Leave empty to deliver every subscribed event.',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'includeFullPayload',
|
|
41
|
+
type: 'checkbox',
|
|
42
|
+
defaultValue: false,
|
|
43
|
+
admin: {
|
|
44
|
+
description: 'If true, send the entire event data block. If false, send only IDs and slugs to keep payloads small.',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'timeoutSeconds',
|
|
49
|
+
type: 'number',
|
|
50
|
+
defaultValue: WEBHOOK_DEFAULT_TIMEOUT_SECONDS,
|
|
51
|
+
admin: { description: `Per-request timeout in seconds. Default ${WEBHOOK_DEFAULT_TIMEOUT_SECONDS}.` },
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
//# sourceMappingURL=config-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-fields.js","sourceRoot":"","sources":["../../../src/integrations/webhook/config-fields.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,wBAAwB,EAAE;IAC5D,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,0BAA0B,EAAE;IAChE,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,0BAA0B,EAAE;IAChE,EAAE,KAAK,EAAE,0BAA0B,EAAE,KAAK,EAAE,0BAA0B,EAAE;IACxE,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,kBAAkB,EAAE;CAChD,CAAA;AAEV,MAAM,CAAC,MAAM,+BAA+B,GAAG,EAAE,CAAA;AACjD,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAA;AAE3C,MAAM,CAAC,MAAM,YAAY,GAAY;IACnC;QACE,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,EAAE,WAAW,EAAE,4CAA4C,EAAE;KACrE;IACD;QACE,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE;YACL,WAAW,EACT,2DAA2D,yBAAyB,qHAAqH;SAC5M;KACF;IACD;QACE,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/E,KAAK,EAAE;YACL,WAAW,EAAE,gFAAgF;SAC9F;KACF;IACD;QACE,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,UAAU;QAChB,YAAY,EAAE,KAAK;QACnB,KAAK,EAAE;YACL,WAAW,EACT,sGAAsG;SACzG;KACF;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,+BAA+B;QAC7C,KAAK,EAAE,EAAE,WAAW,EAAE,2CAA2C,+BAA+B,GAAG,EAAE;KACtG;CACF,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { InngestFunction } from 'inngest';
|
|
2
|
+
import type { IntegrationContext } from '../../types.js';
|
|
3
|
+
export declare const WEBHOOK_INTEGRATION_ID = "webhook";
|
|
4
|
+
/**
|
|
5
|
+
* Builds the two Inngest functions the webhook integration contributes:
|
|
6
|
+
*
|
|
7
|
+
* - `webhook-deliver` subscribes to the system events configured at the
|
|
8
|
+
* admin layer. For every enabled instance whose `eventFilter` matches,
|
|
9
|
+
* it POSTs an HMAC-signed body to the target URL, retries up to five
|
|
10
|
+
* times on failure, and records audit + status updates.
|
|
11
|
+
* - `webhook-manual-trigger` listens for `integration/manual-sync` events
|
|
12
|
+
* (fired by the `trigger_sync` MCP tool). It delivers a minimal test
|
|
13
|
+
* payload to a single instance so admins can verify connectivity from
|
|
14
|
+
* the conversation surface.
|
|
15
|
+
*
|
|
16
|
+
* Failures are isolated: each delivery is its own `step.run`, so a broken
|
|
17
|
+
* instance retries on its own without affecting the others or the
|
|
18
|
+
* publishing pipeline that fired the originating event.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createWebhookFunctions(ctx: IntegrationContext): InngestFunction.Any[];
|
|
21
|
+
//# sourceMappingURL=functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/integrations/webhook/functions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAKxD,eAAO,MAAM,sBAAsB,YAAY,CAAA;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,kBAAkB,GAAG,eAAe,CAAC,GAAG,EAAE,CA2DrF"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { hmacSha256Hex } from './hmac.js';
|
|
2
|
+
import { extractIds } from './payload.js';
|
|
3
|
+
export const WEBHOOK_INTEGRATION_ID = 'webhook';
|
|
4
|
+
/**
|
|
5
|
+
* Builds the two Inngest functions the webhook integration contributes:
|
|
6
|
+
*
|
|
7
|
+
* - `webhook-deliver` subscribes to the system events configured at the
|
|
8
|
+
* admin layer. For every enabled instance whose `eventFilter` matches,
|
|
9
|
+
* it POSTs an HMAC-signed body to the target URL, retries up to five
|
|
10
|
+
* times on failure, and records audit + status updates.
|
|
11
|
+
* - `webhook-manual-trigger` listens for `integration/manual-sync` events
|
|
12
|
+
* (fired by the `trigger_sync` MCP tool). It delivers a minimal test
|
|
13
|
+
* payload to a single instance so admins can verify connectivity from
|
|
14
|
+
* the conversation surface.
|
|
15
|
+
*
|
|
16
|
+
* Failures are isolated: each delivery is its own `step.run`, so a broken
|
|
17
|
+
* instance retries on its own without affecting the others or the
|
|
18
|
+
* publishing pipeline that fired the originating event.
|
|
19
|
+
*/
|
|
20
|
+
export function createWebhookFunctions(ctx) {
|
|
21
|
+
const deliver = ctx.inngest.createFunction({
|
|
22
|
+
id: 'webhook-deliver',
|
|
23
|
+
retries: 5,
|
|
24
|
+
triggers: [
|
|
25
|
+
{ event: 'content/page.published' },
|
|
26
|
+
{ event: 'content/page.unpublished' },
|
|
27
|
+
{ event: 'content/page.rolled_back' },
|
|
28
|
+
{ event: 'form/submission.received' },
|
|
29
|
+
{ event: 'approval/decided' },
|
|
30
|
+
],
|
|
31
|
+
}, async ({ event, step }) => {
|
|
32
|
+
const instances = await step.run('load-webhook-instances', () => ctx.loadInstances(WEBHOOK_INTEGRATION_ID));
|
|
33
|
+
for (const instance of instances) {
|
|
34
|
+
const filter = instance.config.eventFilter;
|
|
35
|
+
if (filter && filter.length > 0 && !filter.includes(event.name))
|
|
36
|
+
continue;
|
|
37
|
+
await step.run(`deliver-${instance.id}`, async () => {
|
|
38
|
+
await deliverEvent({ ctx, instance, eventName: event.name, eventData: event.data });
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const manualTrigger = ctx.inngest.createFunction({
|
|
43
|
+
id: 'webhook-manual-trigger',
|
|
44
|
+
triggers: [{ event: 'integration/manual-sync' }],
|
|
45
|
+
}, async ({ event, step }) => {
|
|
46
|
+
const data = (event.data ?? {});
|
|
47
|
+
const targetInstanceId = data.instanceId;
|
|
48
|
+
if (!targetInstanceId)
|
|
49
|
+
return;
|
|
50
|
+
const instances = await step.run('load-target', async () => {
|
|
51
|
+
const all = await ctx.loadInstances(WEBHOOK_INTEGRATION_ID);
|
|
52
|
+
return all.filter((i) => i.id === targetInstanceId);
|
|
53
|
+
});
|
|
54
|
+
for (const instance of instances) {
|
|
55
|
+
await step.run(`deliver-test-${instance.id}`, async () => {
|
|
56
|
+
await deliverEvent({
|
|
57
|
+
ctx,
|
|
58
|
+
instance,
|
|
59
|
+
eventName: 'integration/manual-sync',
|
|
60
|
+
eventData: { instanceId: instance.id, message: 'Manual sync triggered' },
|
|
61
|
+
includeFullPayloadOverride: true,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return [deliver, manualTrigger];
|
|
67
|
+
}
|
|
68
|
+
async function deliverEvent({ ctx, instance, eventName, eventData, includeFullPayloadOverride, }) {
|
|
69
|
+
const includeFull = includeFullPayloadOverride ?? instance.config.includeFullPayload ?? false;
|
|
70
|
+
const body = JSON.stringify({
|
|
71
|
+
event: eventName,
|
|
72
|
+
data: includeFull ? eventData : extractIds(eventData),
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
instanceId: instance.id,
|
|
75
|
+
});
|
|
76
|
+
const signature = await hmacSha256Hex(body, instance.config.signingSecret);
|
|
77
|
+
const timeoutMs = (instance.config.timeoutSeconds ?? 10) * 1000;
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
80
|
+
let response;
|
|
81
|
+
try {
|
|
82
|
+
response = await fetch(instance.config.targetUrl, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'content-type': 'application/json',
|
|
86
|
+
'x-throughline-event': eventName,
|
|
87
|
+
'x-throughline-signature': `sha256=${signature}`,
|
|
88
|
+
'x-throughline-timestamp': String(Date.now()),
|
|
89
|
+
},
|
|
90
|
+
body,
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
await ctx.updateStatus(instance.id, 'failed', message);
|
|
97
|
+
await ctx.recordAudit({
|
|
98
|
+
integrationId: WEBHOOK_INTEGRATION_ID,
|
|
99
|
+
instanceName: instance.name,
|
|
100
|
+
action: 'integration.failed',
|
|
101
|
+
summary: `Failed to deliver ${eventName} to ${instance.name}: ${message}`,
|
|
102
|
+
errorMessage: message,
|
|
103
|
+
});
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
const success = response.ok;
|
|
110
|
+
await ctx.updateStatus(instance.id, success ? 'success' : 'failed', success ? undefined : `HTTP ${response.status}`);
|
|
111
|
+
await ctx.recordAudit({
|
|
112
|
+
integrationId: WEBHOOK_INTEGRATION_ID,
|
|
113
|
+
instanceName: instance.name,
|
|
114
|
+
action: success ? 'integration.synced' : 'integration.failed',
|
|
115
|
+
summary: success
|
|
116
|
+
? `Delivered ${eventName} to ${instance.name}`
|
|
117
|
+
: `Failed to deliver ${eventName} to ${instance.name} (HTTP ${response.status})`,
|
|
118
|
+
...(success ? {} : { errorMessage: `HTTP ${response.status}` }),
|
|
119
|
+
});
|
|
120
|
+
if (!success) {
|
|
121
|
+
throw new Error(`Webhook delivery failed: HTTP ${response.status}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=functions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"functions.js","sourceRoot":"","sources":["../../../src/integrations/webhook/functions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IntegrationHealth } from '../../types.js';
|
|
2
|
+
import type { WebhookConfig } from './config-fields.js';
|
|
3
|
+
/**
|
|
4
|
+
* Light reachability check for the webhook target. Sends a HEAD request and
|
|
5
|
+
* accepts any 2xx, plus 405 (Method Not Allowed) since plenty of webhook
|
|
6
|
+
* receivers only allow POST and reject HEAD with 405. That's healthy enough
|
|
7
|
+
* for a "your endpoint is up" probe.
|
|
8
|
+
*/
|
|
9
|
+
export declare function healthcheck(config: WebhookConfig): Promise<IntegrationHealth>;
|
|
10
|
+
//# sourceMappingURL=healthcheck.d.ts.map
|