@orgloop/connector-webhook 0.1.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/LICENSE.md +21 -0
- package/README.md +150 -0
- package/dist/__tests__/webhook.test.d.ts +2 -0
- package/dist/__tests__/webhook.test.d.ts.map +1 -0
- package/dist/__tests__/webhook.test.js +567 -0
- package/dist/__tests__/webhook.test.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/source.d.ts +19 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +148 -0
- package/dist/source.js.map +1 -0
- package/dist/target.d.ts +16 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +136 -0
- package/dist/target.js.map +1 -0
- package/package.json +32 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OrgLoop contributors
|
|
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
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# @orgloop/connector-webhook
|
|
2
|
+
|
|
3
|
+
Generic webhook connector providing both a **source** (inbound webhook receiver) and a **target** (outbound HTTP POST). Use it to integrate with any system that can send or receive HTTP webhooks.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @orgloop/connector-webhook
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Source: Inbound Webhook Receiver
|
|
12
|
+
|
|
13
|
+
### Configuration
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
sources:
|
|
17
|
+
- id: webhook-inbound
|
|
18
|
+
connector: "@orgloop/connector-webhook"
|
|
19
|
+
config:
|
|
20
|
+
secret: "${WEBHOOK_SECRET}" # optional — HMAC-SHA256 secret for signature validation
|
|
21
|
+
event_type_field: "type" # optional — dot-path to extract event type from payload (default: "type")
|
|
22
|
+
poll:
|
|
23
|
+
interval: "30s" # how often to drain received webhook events
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
#### Source config options
|
|
27
|
+
|
|
28
|
+
| Field | Type | Required | Default | Description |
|
|
29
|
+
|-------|------|----------|---------|-------------|
|
|
30
|
+
| `secret` | `string` | no | — | HMAC-SHA256 secret for validating signatures. Supports `${ENV_VAR}` syntax |
|
|
31
|
+
| `event_type_field` | `string` | no | `"type"` | Dot-notation path to extract a platform event type from the incoming JSON payload |
|
|
32
|
+
|
|
33
|
+
### Events emitted
|
|
34
|
+
|
|
35
|
+
Incoming webhooks are normalized to `resource.changed` events. The entire JSON body becomes the event `payload`.
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"id": "evt_a1b2c3d4e5f67890",
|
|
40
|
+
"timestamp": "2025-01-15T10:30:00.000Z",
|
|
41
|
+
"source": "webhook",
|
|
42
|
+
"type": "resource.changed",
|
|
43
|
+
"provenance": {
|
|
44
|
+
"platform": "webhook",
|
|
45
|
+
"platform_event": "deployment"
|
|
46
|
+
},
|
|
47
|
+
"payload": {
|
|
48
|
+
"type": "deployment",
|
|
49
|
+
"repo": "my-app",
|
|
50
|
+
"status": "success"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Signature validation
|
|
56
|
+
|
|
57
|
+
If `secret` is configured, the connector validates incoming requests using HMAC-SHA256. It checks for signatures in these headers (in order):
|
|
58
|
+
|
|
59
|
+
1. `x-hub-signature-256` (GitHub-style)
|
|
60
|
+
2. `x-signature`
|
|
61
|
+
|
|
62
|
+
Expected format: `sha256=<hex-digest>`.
|
|
63
|
+
|
|
64
|
+
Requests with missing or invalid signatures receive a 401 response.
|
|
65
|
+
|
|
66
|
+
### Webhook endpoint
|
|
67
|
+
|
|
68
|
+
- **Method**: `POST` only (others get 405)
|
|
69
|
+
- **Content-Type**: `application/json`
|
|
70
|
+
- **Response**: `200 { "ok": true, "event_id": "evt_..." }` on success
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Target: Outbound HTTP Webhook
|
|
75
|
+
|
|
76
|
+
### Configuration
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
actors:
|
|
80
|
+
- id: webhook-notify
|
|
81
|
+
connector: "@orgloop/connector-webhook"
|
|
82
|
+
config:
|
|
83
|
+
url: "https://example.com/hook" # destination URL (required)
|
|
84
|
+
method: "POST" # optional — POST or PUT (default: POST)
|
|
85
|
+
headers: # optional — custom headers
|
|
86
|
+
X-Custom-Header: "my-value"
|
|
87
|
+
auth: # optional — authentication
|
|
88
|
+
type: "bearer" # "bearer" or "basic"
|
|
89
|
+
token: "${WEBHOOK_AUTH_TOKEN}" # for bearer auth (env var ref)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Target config options
|
|
93
|
+
|
|
94
|
+
| Field | Type | Required | Default | Description |
|
|
95
|
+
|-------|------|----------|---------|-------------|
|
|
96
|
+
| `url` | `string` | yes | — | Destination URL to POST/PUT events to |
|
|
97
|
+
| `method` | `string` | no | `"POST"` | HTTP method (`POST` or `PUT`) |
|
|
98
|
+
| `headers` | `object` | no | — | Custom HTTP headers to include |
|
|
99
|
+
| `auth.type` | `string` | no | — | Auth type: `"bearer"` or `"basic"` |
|
|
100
|
+
| `auth.token` | `string` | no | — | Bearer token (for `type: "bearer"`). Supports `${ENV_VAR}` |
|
|
101
|
+
| `auth.username` | `string` | no | — | Username (for `type: "basic"`). Supports `${ENV_VAR}` |
|
|
102
|
+
| `auth.password` | `string` | no | — | Password (for `type: "basic"`). Supports `${ENV_VAR}` |
|
|
103
|
+
|
|
104
|
+
### Delivery payload
|
|
105
|
+
|
|
106
|
+
The target sends a JSON body containing the full event and the resolved launch prompt:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"event": { "id": "evt_...", "type": "resource.changed", "..." : "..." },
|
|
111
|
+
"launch_prompt": "Resolved prompt text from route's with.prompt_file"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Delivery results
|
|
116
|
+
|
|
117
|
+
| HTTP Status | Result |
|
|
118
|
+
|-------------|--------|
|
|
119
|
+
| 2xx | `delivered` |
|
|
120
|
+
| 429 | `error` (rate limited, eligible for retry) |
|
|
121
|
+
| 4xx | `rejected` (client error, not retried) |
|
|
122
|
+
| 5xx | `error` (server error, eligible for retry) |
|
|
123
|
+
| Network error | `error` |
|
|
124
|
+
|
|
125
|
+
## Example route
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
routes:
|
|
129
|
+
- name: deploy-webhook-to-slack
|
|
130
|
+
when:
|
|
131
|
+
source: webhook-inbound
|
|
132
|
+
events:
|
|
133
|
+
- resource.changed
|
|
134
|
+
filter:
|
|
135
|
+
provenance.platform_event: deployment
|
|
136
|
+
then:
|
|
137
|
+
actor: webhook-notify
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Auth / prerequisites
|
|
141
|
+
|
|
142
|
+
- **Source**: No tokens needed. Optionally configure a shared secret for HMAC validation.
|
|
143
|
+
- **Target**: Depends on the destination. Configure `auth` if the endpoint requires it.
|
|
144
|
+
|
|
145
|
+
## Limitations / known issues
|
|
146
|
+
|
|
147
|
+
- **Source is push-based** -- Events are buffered in memory until the next `poll()` call drains them. A crash between receipt and poll loses events.
|
|
148
|
+
- **No batching** -- The target sends one HTTP request per event.
|
|
149
|
+
- **No custom body templates** -- The target always sends `{ event, launch_prompt }`. For custom payload shapes, use a transform before delivery.
|
|
150
|
+
- **Nested event type extraction** -- The `event_type_field` supports dot-notation (e.g., `"data.event_type"`) for extracting the platform event type from deeply nested payloads.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/webhook.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { WebhookSource } from '../source.js';
|
|
8
|
+
import { WebhookTarget } from '../target.js';
|
|
9
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
10
|
+
function createMockRequest(body, method = 'POST', headers = {}) {
|
|
11
|
+
const req = new EventEmitter();
|
|
12
|
+
req.method = method;
|
|
13
|
+
req.headers = headers;
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
req.emit('data', Buffer.from(body));
|
|
16
|
+
req.emit('end');
|
|
17
|
+
}, 0);
|
|
18
|
+
return req;
|
|
19
|
+
}
|
|
20
|
+
function createMockResponse() {
|
|
21
|
+
const res = {
|
|
22
|
+
statusCode: 200,
|
|
23
|
+
body: '',
|
|
24
|
+
writeHead(code, _headers) {
|
|
25
|
+
res.statusCode = code;
|
|
26
|
+
return res;
|
|
27
|
+
},
|
|
28
|
+
end(data) {
|
|
29
|
+
res.body = data ?? '';
|
|
30
|
+
return res;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
return res;
|
|
34
|
+
}
|
|
35
|
+
function signPayload(body, secret) {
|
|
36
|
+
return `sha256=${createHmac('sha256', secret).update(body).digest('hex')}`;
|
|
37
|
+
}
|
|
38
|
+
// ─── WebhookSource ────────────────────────────────────────────────────────────
|
|
39
|
+
describe('WebhookSource', () => {
|
|
40
|
+
it('initializes and returns empty poll', async () => {
|
|
41
|
+
const source = new WebhookSource();
|
|
42
|
+
await source.init({
|
|
43
|
+
id: 'webhook',
|
|
44
|
+
connector: '@orgloop/connector-webhook',
|
|
45
|
+
config: { path: '/webhook' },
|
|
46
|
+
});
|
|
47
|
+
const result = await source.poll(null);
|
|
48
|
+
expect(result.events).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
it('receives webhook and normalizes to OrgLoop event', async () => {
|
|
51
|
+
const source = new WebhookSource();
|
|
52
|
+
await source.init({ id: 'webhook', connector: '@orgloop/connector-webhook', config: {} });
|
|
53
|
+
const handler = source.webhook();
|
|
54
|
+
const payload = { type: 'deployment', repo: 'my-app', status: 'success' };
|
|
55
|
+
const req = createMockRequest(JSON.stringify(payload));
|
|
56
|
+
const res = createMockResponse();
|
|
57
|
+
const events = await handler(req, res);
|
|
58
|
+
expect(res.statusCode).toBe(200);
|
|
59
|
+
expect(events).toHaveLength(1);
|
|
60
|
+
expect(events[0].type).toBe('resource.changed');
|
|
61
|
+
expect(events[0].source).toBe('webhook');
|
|
62
|
+
expect(events[0].provenance.platform).toBe('webhook');
|
|
63
|
+
expect(events[0].provenance.platform_event).toBe('deployment');
|
|
64
|
+
expect(events[0].payload).toEqual(payload);
|
|
65
|
+
// Drain via poll
|
|
66
|
+
const result = await source.poll(null);
|
|
67
|
+
expect(result.events).toHaveLength(1);
|
|
68
|
+
});
|
|
69
|
+
it('rejects non-POST', async () => {
|
|
70
|
+
const source = new WebhookSource();
|
|
71
|
+
await source.init({ id: 'webhook', connector: '@orgloop/connector-webhook', config: {} });
|
|
72
|
+
const handler = source.webhook();
|
|
73
|
+
const req = createMockRequest('', 'GET');
|
|
74
|
+
const res = createMockResponse();
|
|
75
|
+
const events = await handler(req, res);
|
|
76
|
+
expect(res.statusCode).toBe(405);
|
|
77
|
+
expect(events).toHaveLength(0);
|
|
78
|
+
});
|
|
79
|
+
it('rejects invalid JSON', async () => {
|
|
80
|
+
const source = new WebhookSource();
|
|
81
|
+
await source.init({ id: 'webhook', connector: '@orgloop/connector-webhook', config: {} });
|
|
82
|
+
const handler = source.webhook();
|
|
83
|
+
const req = createMockRequest('{invalid');
|
|
84
|
+
const res = createMockResponse();
|
|
85
|
+
const events = await handler(req, res);
|
|
86
|
+
expect(res.statusCode).toBe(400);
|
|
87
|
+
expect(events).toHaveLength(0);
|
|
88
|
+
});
|
|
89
|
+
it('clears pending events on shutdown', async () => {
|
|
90
|
+
const source = new WebhookSource();
|
|
91
|
+
await source.init({ id: 'webhook', connector: '@orgloop/connector-webhook', config: {} });
|
|
92
|
+
const handler = source.webhook();
|
|
93
|
+
const req = createMockRequest(JSON.stringify({ test: true }));
|
|
94
|
+
const res = createMockResponse();
|
|
95
|
+
await handler(req, res);
|
|
96
|
+
await source.shutdown();
|
|
97
|
+
const result = await source.poll(null);
|
|
98
|
+
expect(result.events).toHaveLength(0);
|
|
99
|
+
});
|
|
100
|
+
// ─── HMAC Validation ────────────────────────────────────────────────────
|
|
101
|
+
describe('HMAC validation', () => {
|
|
102
|
+
const SECRET = 'test-webhook-secret';
|
|
103
|
+
it('accepts valid HMAC-SHA256 signature via x-hub-signature-256', async () => {
|
|
104
|
+
const source = new WebhookSource();
|
|
105
|
+
await source.init({
|
|
106
|
+
id: 'webhook',
|
|
107
|
+
connector: '@orgloop/connector-webhook',
|
|
108
|
+
config: { secret: SECRET },
|
|
109
|
+
});
|
|
110
|
+
const handler = source.webhook();
|
|
111
|
+
const body = JSON.stringify({ type: 'push', ref: 'refs/heads/main' });
|
|
112
|
+
const signature = signPayload(body, SECRET);
|
|
113
|
+
const req = createMockRequest(body, 'POST', { 'x-hub-signature-256': signature });
|
|
114
|
+
const res = createMockResponse();
|
|
115
|
+
const events = await handler(req, res);
|
|
116
|
+
expect(res.statusCode).toBe(200);
|
|
117
|
+
expect(events).toHaveLength(1);
|
|
118
|
+
expect(events[0].provenance.platform_event).toBe('push');
|
|
119
|
+
});
|
|
120
|
+
it('accepts valid HMAC-SHA256 signature via x-signature header', async () => {
|
|
121
|
+
const source = new WebhookSource();
|
|
122
|
+
await source.init({
|
|
123
|
+
id: 'webhook',
|
|
124
|
+
connector: '@orgloop/connector-webhook',
|
|
125
|
+
config: { secret: SECRET },
|
|
126
|
+
});
|
|
127
|
+
const handler = source.webhook();
|
|
128
|
+
const body = JSON.stringify({ type: 'deploy', env: 'prod' });
|
|
129
|
+
const signature = signPayload(body, SECRET);
|
|
130
|
+
const req = createMockRequest(body, 'POST', { 'x-signature': signature });
|
|
131
|
+
const res = createMockResponse();
|
|
132
|
+
const events = await handler(req, res);
|
|
133
|
+
expect(res.statusCode).toBe(200);
|
|
134
|
+
expect(events).toHaveLength(1);
|
|
135
|
+
});
|
|
136
|
+
it('rejects invalid signature with 401', async () => {
|
|
137
|
+
const source = new WebhookSource();
|
|
138
|
+
await source.init({
|
|
139
|
+
id: 'webhook',
|
|
140
|
+
connector: '@orgloop/connector-webhook',
|
|
141
|
+
config: { secret: SECRET },
|
|
142
|
+
});
|
|
143
|
+
const handler = source.webhook();
|
|
144
|
+
const body = JSON.stringify({ type: 'push' });
|
|
145
|
+
const req = createMockRequest(body, 'POST', {
|
|
146
|
+
'x-hub-signature-256': 'sha256=invalid_signature_value',
|
|
147
|
+
});
|
|
148
|
+
const res = createMockResponse();
|
|
149
|
+
const events = await handler(req, res);
|
|
150
|
+
expect(res.statusCode).toBe(401);
|
|
151
|
+
expect(events).toHaveLength(0);
|
|
152
|
+
expect(JSON.parse(res.body).error).toBe('Invalid signature');
|
|
153
|
+
});
|
|
154
|
+
it('rejects missing signature when secret is configured with 401', async () => {
|
|
155
|
+
const source = new WebhookSource();
|
|
156
|
+
await source.init({
|
|
157
|
+
id: 'webhook',
|
|
158
|
+
connector: '@orgloop/connector-webhook',
|
|
159
|
+
config: { secret: SECRET },
|
|
160
|
+
});
|
|
161
|
+
const handler = source.webhook();
|
|
162
|
+
const body = JSON.stringify({ type: 'push' });
|
|
163
|
+
const req = createMockRequest(body, 'POST');
|
|
164
|
+
const res = createMockResponse();
|
|
165
|
+
const events = await handler(req, res);
|
|
166
|
+
expect(res.statusCode).toBe(401);
|
|
167
|
+
expect(events).toHaveLength(0);
|
|
168
|
+
expect(JSON.parse(res.body).error).toBe('Missing signature');
|
|
169
|
+
});
|
|
170
|
+
it('accepts all requests when no secret is configured', async () => {
|
|
171
|
+
const source = new WebhookSource();
|
|
172
|
+
await source.init({
|
|
173
|
+
id: 'webhook',
|
|
174
|
+
connector: '@orgloop/connector-webhook',
|
|
175
|
+
config: {},
|
|
176
|
+
});
|
|
177
|
+
const handler = source.webhook();
|
|
178
|
+
const body = JSON.stringify({ type: 'anything' });
|
|
179
|
+
const req = createMockRequest(body, 'POST');
|
|
180
|
+
const res = createMockResponse();
|
|
181
|
+
const events = await handler(req, res);
|
|
182
|
+
expect(res.statusCode).toBe(200);
|
|
183
|
+
expect(events).toHaveLength(1);
|
|
184
|
+
});
|
|
185
|
+
it('prefers x-hub-signature-256 over x-signature', async () => {
|
|
186
|
+
const source = new WebhookSource();
|
|
187
|
+
await source.init({
|
|
188
|
+
id: 'webhook',
|
|
189
|
+
connector: '@orgloop/connector-webhook',
|
|
190
|
+
config: { secret: SECRET },
|
|
191
|
+
});
|
|
192
|
+
const handler = source.webhook();
|
|
193
|
+
const body = JSON.stringify({ type: 'push' });
|
|
194
|
+
const validSig = signPayload(body, SECRET);
|
|
195
|
+
const req = createMockRequest(body, 'POST', {
|
|
196
|
+
'x-hub-signature-256': validSig,
|
|
197
|
+
'x-signature': 'sha256=wrong',
|
|
198
|
+
});
|
|
199
|
+
const res = createMockResponse();
|
|
200
|
+
const events = await handler(req, res);
|
|
201
|
+
// Should succeed because x-hub-signature-256 is valid (checked first)
|
|
202
|
+
expect(res.statusCode).toBe(200);
|
|
203
|
+
expect(events).toHaveLength(1);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
// ─── Buffer Persistence ─────────────────────────────────────────────────
|
|
207
|
+
describe('buffer persistence', () => {
|
|
208
|
+
const bufferDir = join(tmpdir(), `webhook-test-${Date.now()}`);
|
|
209
|
+
afterEach(() => {
|
|
210
|
+
if (existsSync(bufferDir)) {
|
|
211
|
+
rmSync(bufferDir, { recursive: true });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
it('persists events to JSONL file', async () => {
|
|
215
|
+
const source = new WebhookSource();
|
|
216
|
+
await source.init({
|
|
217
|
+
id: 'test-src',
|
|
218
|
+
connector: '@orgloop/connector-webhook',
|
|
219
|
+
config: { buffer_dir: bufferDir },
|
|
220
|
+
});
|
|
221
|
+
const handler = source.webhook();
|
|
222
|
+
const body = JSON.stringify({ type: 'deploy' });
|
|
223
|
+
const req = createMockRequest(body);
|
|
224
|
+
const res = createMockResponse();
|
|
225
|
+
await handler(req, res);
|
|
226
|
+
expect(res.statusCode).toBe(200);
|
|
227
|
+
// Verify the file exists with content
|
|
228
|
+
const bufferPath = join(bufferDir, 'webhook-test-src.jsonl');
|
|
229
|
+
expect(existsSync(bufferPath)).toBe(true);
|
|
230
|
+
const content = readFileSync(bufferPath, 'utf-8').trim();
|
|
231
|
+
const lines = content.split('\n');
|
|
232
|
+
expect(lines).toHaveLength(1);
|
|
233
|
+
const event = JSON.parse(lines[0]);
|
|
234
|
+
expect(event.type).toBe('resource.changed');
|
|
235
|
+
});
|
|
236
|
+
it('drains buffer on poll and truncates file', async () => {
|
|
237
|
+
const source = new WebhookSource();
|
|
238
|
+
await source.init({
|
|
239
|
+
id: 'test-src',
|
|
240
|
+
connector: '@orgloop/connector-webhook',
|
|
241
|
+
config: { buffer_dir: bufferDir },
|
|
242
|
+
});
|
|
243
|
+
const handler = source.webhook();
|
|
244
|
+
// Send two webhooks
|
|
245
|
+
for (let i = 0; i < 2; i++) {
|
|
246
|
+
const body = JSON.stringify({ type: 'event', index: i });
|
|
247
|
+
const req = createMockRequest(body);
|
|
248
|
+
const res = createMockResponse();
|
|
249
|
+
await handler(req, res);
|
|
250
|
+
}
|
|
251
|
+
// Poll should return both events
|
|
252
|
+
const result = await source.poll(null);
|
|
253
|
+
expect(result.events).toHaveLength(2);
|
|
254
|
+
// Buffer file should be empty after poll
|
|
255
|
+
const bufferPath = join(bufferDir, 'webhook-test-src.jsonl');
|
|
256
|
+
const content = readFileSync(bufferPath, 'utf-8');
|
|
257
|
+
expect(content).toBe('');
|
|
258
|
+
// Next poll should return zero events
|
|
259
|
+
const result2 = await source.poll(null);
|
|
260
|
+
expect(result2.events).toHaveLength(0);
|
|
261
|
+
});
|
|
262
|
+
it('creates buffer directory if it does not exist', async () => {
|
|
263
|
+
const nestedDir = join(bufferDir, 'nested', 'dir');
|
|
264
|
+
const source = new WebhookSource();
|
|
265
|
+
await source.init({
|
|
266
|
+
id: 'test-src',
|
|
267
|
+
connector: '@orgloop/connector-webhook',
|
|
268
|
+
config: { buffer_dir: nestedDir },
|
|
269
|
+
});
|
|
270
|
+
expect(existsSync(nestedDir)).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
it('survives simulated restart by reading existing buffer', async () => {
|
|
273
|
+
// Create buffer directory and write events directly
|
|
274
|
+
mkdirSync(bufferDir, { recursive: true });
|
|
275
|
+
const bufferPath = join(bufferDir, 'webhook-persist.jsonl');
|
|
276
|
+
const fakeEvent = {
|
|
277
|
+
id: 'evt_test123',
|
|
278
|
+
timestamp: new Date().toISOString(),
|
|
279
|
+
source: 'webhook',
|
|
280
|
+
type: 'resource.changed',
|
|
281
|
+
provenance: { platform: 'webhook' },
|
|
282
|
+
payload: { survived: true },
|
|
283
|
+
};
|
|
284
|
+
writeFileSync(bufferPath, `${JSON.stringify(fakeEvent)}\n`);
|
|
285
|
+
// Init source — should pick up the pre-existing buffer
|
|
286
|
+
const source = new WebhookSource();
|
|
287
|
+
await source.init({
|
|
288
|
+
id: 'persist',
|
|
289
|
+
connector: '@orgloop/connector-webhook',
|
|
290
|
+
config: { buffer_dir: bufferDir },
|
|
291
|
+
});
|
|
292
|
+
const result = await source.poll(null);
|
|
293
|
+
expect(result.events).toHaveLength(1);
|
|
294
|
+
expect(result.events[0].payload).toEqual({ survived: true });
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
// ─── WebhookTarget ────────────────────────────────────────────────────────────
|
|
299
|
+
describe('WebhookTarget', () => {
|
|
300
|
+
let fetchMock;
|
|
301
|
+
beforeEach(() => {
|
|
302
|
+
fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: 'OK' });
|
|
303
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
304
|
+
});
|
|
305
|
+
afterEach(() => {
|
|
306
|
+
vi.restoreAllMocks();
|
|
307
|
+
});
|
|
308
|
+
it('delivers event to configured URL', async () => {
|
|
309
|
+
const target = new WebhookTarget();
|
|
310
|
+
await target.init({
|
|
311
|
+
id: 'webhook-target',
|
|
312
|
+
connector: '@orgloop/connector-webhook',
|
|
313
|
+
config: { url: 'https://example.com/hook' },
|
|
314
|
+
});
|
|
315
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
316
|
+
const event = createTestEvent();
|
|
317
|
+
const result = await target.deliver(event, {});
|
|
318
|
+
expect(result.status).toBe('delivered');
|
|
319
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
320
|
+
const [url] = fetchMock.mock.calls[0];
|
|
321
|
+
expect(url).toBe('https://example.com/hook');
|
|
322
|
+
});
|
|
323
|
+
it('sends event and launch_prompt in body', async () => {
|
|
324
|
+
const target = new WebhookTarget();
|
|
325
|
+
await target.init({
|
|
326
|
+
id: 'webhook-target',
|
|
327
|
+
connector: '@orgloop/connector-webhook',
|
|
328
|
+
config: { url: 'https://example.com/hook' },
|
|
329
|
+
});
|
|
330
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
331
|
+
const event = createTestEvent();
|
|
332
|
+
await target.deliver(event, { launch_prompt: 'Do the thing' });
|
|
333
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
334
|
+
expect(body.event.id).toBe(event.id);
|
|
335
|
+
expect(body.launch_prompt).toBe('Do the thing');
|
|
336
|
+
});
|
|
337
|
+
it('handles 429 rate limit', async () => {
|
|
338
|
+
fetchMock.mockResolvedValueOnce({ ok: false, status: 429, statusText: 'Too Many Requests' });
|
|
339
|
+
const target = new WebhookTarget();
|
|
340
|
+
await target.init({
|
|
341
|
+
id: 'webhook-target',
|
|
342
|
+
connector: '@orgloop/connector-webhook',
|
|
343
|
+
config: { url: 'https://example.com/hook' },
|
|
344
|
+
});
|
|
345
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
346
|
+
const result = await target.deliver(createTestEvent(), {});
|
|
347
|
+
expect(result.status).toBe('error');
|
|
348
|
+
expect(result.error?.message).toContain('429');
|
|
349
|
+
});
|
|
350
|
+
it('handles 4xx rejection', async () => {
|
|
351
|
+
fetchMock.mockResolvedValueOnce({ ok: false, status: 422, statusText: 'Unprocessable Entity' });
|
|
352
|
+
const target = new WebhookTarget();
|
|
353
|
+
await target.init({
|
|
354
|
+
id: 'webhook-target',
|
|
355
|
+
connector: '@orgloop/connector-webhook',
|
|
356
|
+
config: { url: 'https://example.com/hook' },
|
|
357
|
+
});
|
|
358
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
359
|
+
const result = await target.deliver(createTestEvent(), {});
|
|
360
|
+
expect(result.status).toBe('rejected');
|
|
361
|
+
});
|
|
362
|
+
it('handles network error', async () => {
|
|
363
|
+
fetchMock.mockRejectedValueOnce(new Error('ECONNREFUSED'));
|
|
364
|
+
const target = new WebhookTarget();
|
|
365
|
+
await target.init({
|
|
366
|
+
id: 'webhook-target',
|
|
367
|
+
connector: '@orgloop/connector-webhook',
|
|
368
|
+
config: { url: 'https://example.com/hook' },
|
|
369
|
+
});
|
|
370
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
371
|
+
const result = await target.deliver(createTestEvent(), {});
|
|
372
|
+
expect(result.status).toBe('error');
|
|
373
|
+
expect(result.error?.message).toContain('ECONNREFUSED');
|
|
374
|
+
});
|
|
375
|
+
// ─── Auth Tests ─────────────────────────────────────────────────────────
|
|
376
|
+
describe('authentication', () => {
|
|
377
|
+
it('sends Bearer token in Authorization header', async () => {
|
|
378
|
+
const target = new WebhookTarget();
|
|
379
|
+
await target.init({
|
|
380
|
+
id: 'webhook-target',
|
|
381
|
+
connector: '@orgloop/connector-webhook',
|
|
382
|
+
config: {
|
|
383
|
+
url: 'https://example.com/hook',
|
|
384
|
+
auth: { type: 'bearer', token: 'my-secret-token' },
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
388
|
+
await target.deliver(createTestEvent(), {});
|
|
389
|
+
const opts = fetchMock.mock.calls[0][1];
|
|
390
|
+
expect(opts.headers.Authorization).toBe('Bearer my-secret-token');
|
|
391
|
+
});
|
|
392
|
+
it('sends Basic auth with base64-encoded credentials', async () => {
|
|
393
|
+
const target = new WebhookTarget();
|
|
394
|
+
await target.init({
|
|
395
|
+
id: 'webhook-target',
|
|
396
|
+
connector: '@orgloop/connector-webhook',
|
|
397
|
+
config: {
|
|
398
|
+
url: 'https://example.com/hook',
|
|
399
|
+
auth: { type: 'basic', username: 'alice', password: 'secret123' },
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
403
|
+
await target.deliver(createTestEvent(), {});
|
|
404
|
+
const opts = fetchMock.mock.calls[0][1];
|
|
405
|
+
const expected = `Basic ${Buffer.from('alice:secret123').toString('base64')}`;
|
|
406
|
+
expect(opts.headers.Authorization).toBe(expected);
|
|
407
|
+
});
|
|
408
|
+
it('sends no Authorization header when no auth configured', async () => {
|
|
409
|
+
const target = new WebhookTarget();
|
|
410
|
+
await target.init({
|
|
411
|
+
id: 'webhook-target',
|
|
412
|
+
connector: '@orgloop/connector-webhook',
|
|
413
|
+
config: { url: 'https://example.com/hook' },
|
|
414
|
+
});
|
|
415
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
416
|
+
await target.deliver(createTestEvent(), {});
|
|
417
|
+
const opts = fetchMock.mock.calls[0][1];
|
|
418
|
+
expect(opts.headers.Authorization).toBeUndefined();
|
|
419
|
+
});
|
|
420
|
+
it('passes custom headers through', async () => {
|
|
421
|
+
const target = new WebhookTarget();
|
|
422
|
+
await target.init({
|
|
423
|
+
id: 'webhook-target',
|
|
424
|
+
connector: '@orgloop/connector-webhook',
|
|
425
|
+
config: {
|
|
426
|
+
url: 'https://example.com/hook',
|
|
427
|
+
headers: { 'X-Custom-Header': 'custom-value', 'X-Request-Id': 'req-123' },
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
431
|
+
await target.deliver(createTestEvent(), {});
|
|
432
|
+
const opts = fetchMock.mock.calls[0][1];
|
|
433
|
+
expect(opts.headers['X-Custom-Header']).toBe('custom-value');
|
|
434
|
+
expect(opts.headers['X-Request-Id']).toBe('req-123');
|
|
435
|
+
expect(opts.headers['Content-Type']).toBe('application/json');
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
// ─── Body Template Tests ────────────────────────────────────────────────
|
|
439
|
+
describe('body template', () => {
|
|
440
|
+
it('falls back to default body when no template configured', async () => {
|
|
441
|
+
const target = new WebhookTarget();
|
|
442
|
+
await target.init({
|
|
443
|
+
id: 'webhook-target',
|
|
444
|
+
connector: '@orgloop/connector-webhook',
|
|
445
|
+
config: { url: 'https://example.com/hook' },
|
|
446
|
+
});
|
|
447
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
448
|
+
const event = createTestEvent();
|
|
449
|
+
await target.deliver(event, { launch_prompt: 'Do something' });
|
|
450
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
451
|
+
expect(body.event.id).toBe(event.id);
|
|
452
|
+
expect(body.launch_prompt).toBe('Do something');
|
|
453
|
+
});
|
|
454
|
+
it('expands simple template with event fields', async () => {
|
|
455
|
+
const target = new WebhookTarget();
|
|
456
|
+
await target.init({
|
|
457
|
+
id: 'webhook-target',
|
|
458
|
+
connector: '@orgloop/connector-webhook',
|
|
459
|
+
config: {
|
|
460
|
+
url: 'https://hooks.slack.com/test',
|
|
461
|
+
body_template: {
|
|
462
|
+
text: '{{ event.provenance.platform_event }} by {{ event.provenance.author }}',
|
|
463
|
+
channel: '#engineering',
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
468
|
+
const event = createTestEvent({
|
|
469
|
+
provenance: {
|
|
470
|
+
platform: 'github',
|
|
471
|
+
platform_event: 'pull_request.opened',
|
|
472
|
+
author: 'alice',
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
await target.deliver(event, {});
|
|
476
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
477
|
+
expect(body.text).toBe('pull_request.opened by alice');
|
|
478
|
+
expect(body.channel).toBe('#engineering');
|
|
479
|
+
});
|
|
480
|
+
it('expands launch_prompt in template', async () => {
|
|
481
|
+
const target = new WebhookTarget();
|
|
482
|
+
await target.init({
|
|
483
|
+
id: 'webhook-target',
|
|
484
|
+
connector: '@orgloop/connector-webhook',
|
|
485
|
+
config: {
|
|
486
|
+
url: 'https://example.com/hook',
|
|
487
|
+
body_template: {
|
|
488
|
+
prompt: '{{ launch_prompt }}',
|
|
489
|
+
source: '{{ event.source }}',
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
});
|
|
493
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
494
|
+
const event = createTestEvent({ source: 'my-source' });
|
|
495
|
+
await target.deliver(event, { launch_prompt: 'Review this PR' });
|
|
496
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
497
|
+
expect(body.prompt).toBe('Review this PR');
|
|
498
|
+
expect(body.source).toBe('my-source');
|
|
499
|
+
});
|
|
500
|
+
it('handles missing template paths gracefully (empty string)', async () => {
|
|
501
|
+
const target = new WebhookTarget();
|
|
502
|
+
await target.init({
|
|
503
|
+
id: 'webhook-target',
|
|
504
|
+
connector: '@orgloop/connector-webhook',
|
|
505
|
+
config: {
|
|
506
|
+
url: 'https://example.com/hook',
|
|
507
|
+
body_template: {
|
|
508
|
+
text: '{{ event.nonexistent.field }}',
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
513
|
+
await target.deliver(createTestEvent(), {});
|
|
514
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
515
|
+
expect(body.text).toBe('');
|
|
516
|
+
});
|
|
517
|
+
it('expands nested template objects', async () => {
|
|
518
|
+
const target = new WebhookTarget();
|
|
519
|
+
await target.init({
|
|
520
|
+
id: 'webhook-target',
|
|
521
|
+
connector: '@orgloop/connector-webhook',
|
|
522
|
+
config: {
|
|
523
|
+
url: 'https://example.com/hook',
|
|
524
|
+
body_template: {
|
|
525
|
+
message: {
|
|
526
|
+
text: '{{ event.provenance.platform }}',
|
|
527
|
+
metadata: {
|
|
528
|
+
event_id: '{{ event.id }}',
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
});
|
|
534
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
535
|
+
const event = createTestEvent({
|
|
536
|
+
provenance: { platform: 'github' },
|
|
537
|
+
});
|
|
538
|
+
await target.deliver(event, {});
|
|
539
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
540
|
+
expect(body.message.text).toBe('github');
|
|
541
|
+
expect(body.message.metadata.event_id).toBe(event.id);
|
|
542
|
+
});
|
|
543
|
+
it('preserves literal values (non-template strings, numbers, booleans)', async () => {
|
|
544
|
+
const target = new WebhookTarget();
|
|
545
|
+
await target.init({
|
|
546
|
+
id: 'webhook-target',
|
|
547
|
+
connector: '@orgloop/connector-webhook',
|
|
548
|
+
config: {
|
|
549
|
+
url: 'https://example.com/hook',
|
|
550
|
+
body_template: {
|
|
551
|
+
text: '{{ event.source }}',
|
|
552
|
+
count: 42,
|
|
553
|
+
active: true,
|
|
554
|
+
static_text: 'hello world',
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
const { createTestEvent } = await import('@orgloop/sdk');
|
|
559
|
+
await target.deliver(createTestEvent(), {});
|
|
560
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body);
|
|
561
|
+
expect(body.count).toBe(42);
|
|
562
|
+
expect(body.active).toBe(true);
|
|
563
|
+
expect(body.static_text).toBe('hello world');
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
//# sourceMappingURL=webhook.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.test.js","sourceRoot":"","sources":["../../src/__tests__/webhook.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAI1D,MAAM,CAAC,OAAO,UAAU,QAAQ,IAAI,qBAAqB,CAexD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @orgloop/connector-webhook — Generic webhook connector registration (source + target).
|
|
3
|
+
*/
|
|
4
|
+
import { WebhookSource } from './source.js';
|
|
5
|
+
import { WebhookTarget } from './target.js';
|
|
6
|
+
export default function register() {
|
|
7
|
+
return {
|
|
8
|
+
id: 'webhook',
|
|
9
|
+
source: WebhookSource,
|
|
10
|
+
target: WebhookTarget,
|
|
11
|
+
setup: {
|
|
12
|
+
env_vars: [
|
|
13
|
+
{
|
|
14
|
+
name: 'WEBHOOK_SECRET',
|
|
15
|
+
description: 'HMAC secret for validating incoming webhook signatures',
|
|
16
|
+
required: false,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,OAAO,UAAU,QAAQ;IAC/B,OAAO;QACN,EAAE,EAAE,SAAS;QACb,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,aAAa;QACrB,KAAK,EAAE;YACN,QAAQ,EAAE;gBACT;oBACC,IAAI,EAAE,gBAAgB;oBACtB,WAAW,EAAE,wDAAwD;oBACrE,QAAQ,EAAE,KAAK;iBACf;aACD;SACD;KACD,CAAC;AACH,CAAC"}
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic webhook source connector — receives inbound webhooks and normalizes to OrgLoop events.
|
|
3
|
+
*/
|
|
4
|
+
import type { PollResult, SourceConfig, SourceConnector, WebhookHandler } from '@orgloop/sdk';
|
|
5
|
+
export declare class WebhookSource implements SourceConnector {
|
|
6
|
+
readonly id = "webhook";
|
|
7
|
+
private secret?;
|
|
8
|
+
private eventTypeField;
|
|
9
|
+
private pendingEvents;
|
|
10
|
+
private sourceId;
|
|
11
|
+
private bufferPath?;
|
|
12
|
+
init(config: SourceConfig): Promise<void>;
|
|
13
|
+
poll(_checkpoint: string | null): Promise<PollResult>;
|
|
14
|
+
webhook(): WebhookHandler;
|
|
15
|
+
shutdown(): Promise<void>;
|
|
16
|
+
private persistEvent;
|
|
17
|
+
private loadBufferedEvents;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAEX,UAAU,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,MAAM,cAAc,CAAC;AAuBtB,qBAAa,aAAc,YAAW,eAAe;IACpD,QAAQ,CAAC,EAAE,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAC,CAAS;IAEtB,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBzC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IAe3D,OAAO,IAAI,cAAc;IA4DnB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,kBAAkB;CAU1B"}
|
package/dist/source.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic webhook source connector — receives inbound webhooks and normalizes to OrgLoop events.
|
|
3
|
+
*/
|
|
4
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
5
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { buildEvent } from '@orgloop/sdk';
|
|
8
|
+
/** Resolve env var references like ${WEBHOOK_SECRET} */
|
|
9
|
+
function resolveEnvVar(value) {
|
|
10
|
+
const match = value.match(/^\$\{(.+)\}$/);
|
|
11
|
+
if (match) {
|
|
12
|
+
const envValue = process.env[match[1]];
|
|
13
|
+
if (!envValue) {
|
|
14
|
+
throw new Error(`Environment variable ${match[1]} is not set`);
|
|
15
|
+
}
|
|
16
|
+
return envValue;
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
export class WebhookSource {
|
|
21
|
+
id = 'webhook';
|
|
22
|
+
secret;
|
|
23
|
+
eventTypeField = 'type';
|
|
24
|
+
pendingEvents = [];
|
|
25
|
+
sourceId = 'webhook';
|
|
26
|
+
bufferPath;
|
|
27
|
+
async init(config) {
|
|
28
|
+
this.sourceId = config.id;
|
|
29
|
+
const cfg = config.config;
|
|
30
|
+
this.eventTypeField = cfg.event_type_field ?? 'type';
|
|
31
|
+
if (cfg.secret) {
|
|
32
|
+
this.secret = resolveEnvVar(cfg.secret);
|
|
33
|
+
}
|
|
34
|
+
if (cfg.buffer_dir) {
|
|
35
|
+
const dir = resolveEnvVar(cfg.buffer_dir);
|
|
36
|
+
if (!existsSync(dir)) {
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
this.bufferPath = join(dir, `webhook-${this.sourceId}.jsonl`);
|
|
40
|
+
this.loadBufferedEvents();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async poll(_checkpoint) {
|
|
44
|
+
let events;
|
|
45
|
+
if (this.bufferPath) {
|
|
46
|
+
events = this.loadBufferedEvents();
|
|
47
|
+
// Truncate the buffer file after reading
|
|
48
|
+
writeFileSync(this.bufferPath, '');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
events = [...this.pendingEvents];
|
|
52
|
+
this.pendingEvents = [];
|
|
53
|
+
}
|
|
54
|
+
const checkpoint = events.length > 0 ? events[events.length - 1].timestamp : new Date().toISOString();
|
|
55
|
+
return { events, checkpoint };
|
|
56
|
+
}
|
|
57
|
+
webhook() {
|
|
58
|
+
return async (req, res) => {
|
|
59
|
+
if (req.method !== 'POST') {
|
|
60
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
61
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const bodyStr = await readBody(req);
|
|
65
|
+
// HMAC validation if secret is configured
|
|
66
|
+
if (this.secret) {
|
|
67
|
+
const signature = req.headers['x-hub-signature-256'] ?? req.headers['x-signature'];
|
|
68
|
+
if (!signature) {
|
|
69
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
70
|
+
res.end(JSON.stringify({ error: 'Missing signature' }));
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const expected = `sha256=${createHmac('sha256', this.secret).update(bodyStr).digest('hex')}`;
|
|
74
|
+
const sigBuffer = Buffer.from(signature);
|
|
75
|
+
const expectedBuffer = Buffer.from(expected);
|
|
76
|
+
if (sigBuffer.length !== expectedBuffer.length ||
|
|
77
|
+
!timingSafeEqual(sigBuffer, expectedBuffer)) {
|
|
78
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
79
|
+
res.end(JSON.stringify({ error: 'Invalid signature' }));
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const payload = JSON.parse(bodyStr);
|
|
85
|
+
const eventType = getNestedValue(payload, this.eventTypeField);
|
|
86
|
+
const event = buildEvent({
|
|
87
|
+
source: this.sourceId,
|
|
88
|
+
type: 'resource.changed',
|
|
89
|
+
provenance: {
|
|
90
|
+
platform: 'webhook',
|
|
91
|
+
platform_event: typeof eventType === 'string' ? eventType : 'unknown',
|
|
92
|
+
},
|
|
93
|
+
payload,
|
|
94
|
+
});
|
|
95
|
+
this.persistEvent(event);
|
|
96
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
97
|
+
res.end(JSON.stringify({ ok: true, event_id: event.id }));
|
|
98
|
+
return [event];
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
102
|
+
res.end(JSON.stringify({ error: 'Invalid JSON payload' }));
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async shutdown() {
|
|
108
|
+
this.pendingEvents = [];
|
|
109
|
+
}
|
|
110
|
+
persistEvent(event) {
|
|
111
|
+
if (this.bufferPath) {
|
|
112
|
+
appendFileSync(this.bufferPath, `${JSON.stringify(event)}\n`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
this.pendingEvents.push(event);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
loadBufferedEvents() {
|
|
119
|
+
if (!this.bufferPath || !existsSync(this.bufferPath)) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const content = readFileSync(this.bufferPath, 'utf-8').trim();
|
|
123
|
+
if (!content) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
return content.split('\n').map((line) => JSON.parse(line));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function readBody(req) {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const chunks = [];
|
|
132
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
133
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
134
|
+
req.on('error', reject);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Get a nested value from an object using dot-notation path */
|
|
138
|
+
function getNestedValue(obj, path) {
|
|
139
|
+
const parts = path.split('.');
|
|
140
|
+
let current = obj;
|
|
141
|
+
for (const part of parts) {
|
|
142
|
+
if (current == null || typeof current !== 'object')
|
|
143
|
+
return undefined;
|
|
144
|
+
current = current[part];
|
|
145
|
+
}
|
|
146
|
+
return current;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"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"}
|
package/dist/target.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic webhook target connector — POSTs events to a configured URL.
|
|
3
|
+
*/
|
|
4
|
+
import type { ActorConfig, ActorConnector, DeliveryResult, OrgLoopEvent, RouteDeliveryConfig } from '@orgloop/sdk';
|
|
5
|
+
export declare class WebhookTarget implements ActorConnector {
|
|
6
|
+
readonly id = "webhook";
|
|
7
|
+
private url;
|
|
8
|
+
private method;
|
|
9
|
+
private customHeaders;
|
|
10
|
+
private authHeader?;
|
|
11
|
+
private bodyTemplate?;
|
|
12
|
+
init(config: ActorConfig): Promise<void>;
|
|
13
|
+
deliver(event: OrgLoopEvent, routeConfig: RouteDeliveryConfig): Promise<DeliveryResult>;
|
|
14
|
+
shutdown(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,cAAc,EACd,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,MAAM,cAAc,CAAC;AAmEtB,qBAAa,aAAc,YAAW,cAAc;IACnD,QAAQ,CAAC,EAAE,aAAa;IACxB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAC,CAA0B;IAEzC,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBxC,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;IA6DvF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
|
package/dist/target.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic webhook target connector — POSTs events to a configured URL.
|
|
3
|
+
*/
|
|
4
|
+
/** Resolve env var references */
|
|
5
|
+
function resolveEnvVar(value) {
|
|
6
|
+
const match = value.match(/^\$\{(.+)\}$/);
|
|
7
|
+
if (match) {
|
|
8
|
+
const envValue = process.env[match[1]];
|
|
9
|
+
if (!envValue) {
|
|
10
|
+
throw new Error(`Environment variable ${match[1]} is not set`);
|
|
11
|
+
}
|
|
12
|
+
return envValue;
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
/** Get a nested value from an object using dot-notation path */
|
|
17
|
+
function getNestedValue(obj, path) {
|
|
18
|
+
const parts = path.split('.');
|
|
19
|
+
let current = obj;
|
|
20
|
+
for (const part of parts) {
|
|
21
|
+
if (current == null || typeof current !== 'object')
|
|
22
|
+
return undefined;
|
|
23
|
+
current = current[part];
|
|
24
|
+
}
|
|
25
|
+
return current;
|
|
26
|
+
}
|
|
27
|
+
/** Expand template strings: replaces {{ path }} with values from context */
|
|
28
|
+
function expandTemplate(template, context) {
|
|
29
|
+
if (typeof template === 'string') {
|
|
30
|
+
// Full-string replacement for single {{ path }} (preserves non-string types)
|
|
31
|
+
const fullMatch = template.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
|
|
32
|
+
if (fullMatch) {
|
|
33
|
+
const value = getNestedValue(context, fullMatch[1]);
|
|
34
|
+
return value ?? '';
|
|
35
|
+
}
|
|
36
|
+
// Inline replacement for strings with embedded {{ path }}
|
|
37
|
+
return template.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, path) => {
|
|
38
|
+
const value = getNestedValue(context, path);
|
|
39
|
+
return value == null ? '' : String(value);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(template)) {
|
|
43
|
+
return template.map((item) => expandTemplate(item, context));
|
|
44
|
+
}
|
|
45
|
+
if (template != null && typeof template === 'object') {
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const [key, value] of Object.entries(template)) {
|
|
48
|
+
result[key] = expandTemplate(value, context);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
return template;
|
|
53
|
+
}
|
|
54
|
+
export class WebhookTarget {
|
|
55
|
+
id = 'webhook';
|
|
56
|
+
url = '';
|
|
57
|
+
method = 'POST';
|
|
58
|
+
customHeaders = {};
|
|
59
|
+
authHeader;
|
|
60
|
+
bodyTemplate;
|
|
61
|
+
async init(config) {
|
|
62
|
+
const cfg = config.config;
|
|
63
|
+
this.url = cfg.url;
|
|
64
|
+
this.method = cfg.method ?? 'POST';
|
|
65
|
+
this.customHeaders = cfg.headers ?? {};
|
|
66
|
+
this.bodyTemplate = cfg.body_template;
|
|
67
|
+
if (cfg.auth) {
|
|
68
|
+
if (cfg.auth.type === 'bearer' && cfg.auth.token) {
|
|
69
|
+
this.authHeader = `Bearer ${resolveEnvVar(cfg.auth.token)}`;
|
|
70
|
+
}
|
|
71
|
+
else if (cfg.auth.type === 'basic' && cfg.auth.username && cfg.auth.password) {
|
|
72
|
+
const credentials = Buffer.from(`${resolveEnvVar(cfg.auth.username)}:${resolveEnvVar(cfg.auth.password)}`).toString('base64');
|
|
73
|
+
this.authHeader = `Basic ${credentials}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async deliver(event, routeConfig) {
|
|
78
|
+
const headers = {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
...this.customHeaders,
|
|
81
|
+
};
|
|
82
|
+
if (this.authHeader) {
|
|
83
|
+
headers.Authorization = this.authHeader;
|
|
84
|
+
}
|
|
85
|
+
let body;
|
|
86
|
+
if (this.bodyTemplate) {
|
|
87
|
+
const context = {
|
|
88
|
+
event,
|
|
89
|
+
launch_prompt: routeConfig.launch_prompt ?? '',
|
|
90
|
+
};
|
|
91
|
+
body = expandTemplate(this.bodyTemplate, context);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
body = {
|
|
95
|
+
event,
|
|
96
|
+
launch_prompt: routeConfig.launch_prompt,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetch(this.url, {
|
|
101
|
+
method: this.method,
|
|
102
|
+
headers,
|
|
103
|
+
body: JSON.stringify(body),
|
|
104
|
+
});
|
|
105
|
+
if (response.ok) {
|
|
106
|
+
return { status: 'delivered' };
|
|
107
|
+
}
|
|
108
|
+
if (response.status === 429) {
|
|
109
|
+
return {
|
|
110
|
+
status: 'error',
|
|
111
|
+
error: new Error('Webhook rate limited (429)'),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (response.status >= 400 && response.status < 500) {
|
|
115
|
+
return {
|
|
116
|
+
status: 'rejected',
|
|
117
|
+
error: new Error(`Webhook rejected: ${response.status} ${response.statusText}`),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
status: 'error',
|
|
122
|
+
error: new Error(`Webhook error: ${response.status} ${response.statusText}`),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
return {
|
|
127
|
+
status: 'error',
|
|
128
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async shutdown() {
|
|
133
|
+
// Nothing to clean up
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target.js","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orgloop/connector-webhook",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OrgLoop generic webhook connector — inbound and outbound",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@orgloop/sdk": "0.1.0"
|
|
10
|
+
},
|
|
11
|
+
"orgloop": {
|
|
12
|
+
"type": "connector",
|
|
13
|
+
"provides": [
|
|
14
|
+
"source",
|
|
15
|
+
"target"
|
|
16
|
+
],
|
|
17
|
+
"id": "webhook"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"test": "vitest run"
|
|
31
|
+
}
|
|
32
|
+
}
|