@gravito/echo 1.0.0-alpha.2
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/README.md +170 -0
- package/dist/index.cjs +2 -0
- package/dist/index.js +487 -0
- package/dist/index.js.map +16 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# @gravito/echo
|
|
2
|
+
|
|
3
|
+
> 📡 Enterprise-grade webhook handling for Gravito. Secure receiving and reliable sending.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Secure Receiving** - HMAC signature verification, timestamp validation
|
|
8
|
+
- **Built-in Providers** - Stripe, GitHub, and generic provider support
|
|
9
|
+
- **Reliable Sending** - Exponential backoff retry with configurable strategy
|
|
10
|
+
- **Gravito Integration** - First-class OrbitEcho module for PlanetCore
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add @gravito/echo
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Receiving Webhooks
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { OrbitEcho, WebhookReceiver } from '@gravito/echo'
|
|
24
|
+
|
|
25
|
+
const core = new PlanetCore()
|
|
26
|
+
|
|
27
|
+
// Install Echo module
|
|
28
|
+
core.install(new OrbitEcho({
|
|
29
|
+
providers: {
|
|
30
|
+
stripe: { name: 'stripe', secret: process.env.STRIPE_WEBHOOK_SECRET! },
|
|
31
|
+
github: { name: 'github', secret: process.env.GITHUB_WEBHOOK_SECRET! }
|
|
32
|
+
}
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
// Get receiver and add handlers
|
|
36
|
+
const receiver = core.container.make<WebhookReceiver>('echo.receiver')
|
|
37
|
+
|
|
38
|
+
// Handle specific events
|
|
39
|
+
receiver.on('stripe', 'payment_intent.succeeded', async (event) => {
|
|
40
|
+
console.log('Payment received:', event.payload)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
receiver.on('github', 'push', async (event) => {
|
|
44
|
+
console.log('Push event:', event.payload)
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Webhook Endpoint
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
app.post('/webhooks/:provider', async (c) => {
|
|
52
|
+
const provider = c.req.param('provider')
|
|
53
|
+
const body = await c.req.text()
|
|
54
|
+
const headers = c.req.raw.headers
|
|
55
|
+
|
|
56
|
+
const receiver = c.get('echo.receiver') as WebhookReceiver
|
|
57
|
+
const result = await receiver.handle(provider, body, Object.fromEntries(headers))
|
|
58
|
+
|
|
59
|
+
if (!result.valid) {
|
|
60
|
+
return c.json({ error: result.error }, 401)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return c.json({ received: true })
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Sending Webhooks
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { WebhookDispatcher } from '@gravito/echo'
|
|
71
|
+
|
|
72
|
+
const dispatcher = new WebhookDispatcher({
|
|
73
|
+
secret: process.env.OUTGOING_WEBHOOK_SECRET!,
|
|
74
|
+
retry: {
|
|
75
|
+
maxAttempts: 5,
|
|
76
|
+
initialDelay: 1000,
|
|
77
|
+
backoffMultiplier: 2
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Send webhook with automatic retry
|
|
82
|
+
const result = await dispatcher.dispatch({
|
|
83
|
+
url: 'https://example.com/webhook',
|
|
84
|
+
event: 'order.created',
|
|
85
|
+
data: { orderId: 123, total: 99.99 }
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (result.success) {
|
|
89
|
+
console.log('Webhook delivered:', result.statusCode)
|
|
90
|
+
} else {
|
|
91
|
+
console.error('Delivery failed:', result.error)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Providers
|
|
96
|
+
|
|
97
|
+
### Built-in Providers
|
|
98
|
+
|
|
99
|
+
| Provider | Signature Method | Header |
|
|
100
|
+
|----------|-----------------|--------|
|
|
101
|
+
| `stripe` | HMAC-SHA256 + Timestamp | `Stripe-Signature` |
|
|
102
|
+
| `github` | HMAC-SHA256 | `X-Hub-Signature-256` |
|
|
103
|
+
| `generic` | HMAC-SHA256 | `X-Webhook-Signature` |
|
|
104
|
+
|
|
105
|
+
### Custom Provider
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { WebhookProvider, WebhookReceiver } from '@gravito/echo'
|
|
109
|
+
|
|
110
|
+
class MyProvider implements WebhookProvider {
|
|
111
|
+
readonly name = 'my-provider'
|
|
112
|
+
|
|
113
|
+
async verify(payload, headers, secret) {
|
|
114
|
+
// Custom verification logic
|
|
115
|
+
return { valid: true, payload: JSON.parse(payload) }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
receiver.registerProviderType('my-provider', MyProvider)
|
|
120
|
+
receiver.registerProvider('custom', 'secret', { type: 'my-provider' })
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Configuration
|
|
124
|
+
|
|
125
|
+
### WebhookDispatcher
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
interface WebhookDispatcherConfig {
|
|
129
|
+
/** Secret for signing outgoing webhooks */
|
|
130
|
+
secret: string
|
|
131
|
+
|
|
132
|
+
/** Retry configuration */
|
|
133
|
+
retry?: {
|
|
134
|
+
maxAttempts?: number // default: 3
|
|
135
|
+
initialDelay?: number // default: 1000ms
|
|
136
|
+
backoffMultiplier?: number // default: 2
|
|
137
|
+
maxDelay?: number // default: 300000ms (5min)
|
|
138
|
+
retryableStatuses?: number[] // default: [408, 429, 500, 502, 503, 504]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Request timeout in ms */
|
|
142
|
+
timeout?: number // default: 30000
|
|
143
|
+
|
|
144
|
+
/** Custom User-Agent */
|
|
145
|
+
userAgent?: string
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### OrbitEcho
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
interface EchoConfig {
|
|
153
|
+
/** Registered webhook providers */
|
|
154
|
+
providers?: Record<string, {
|
|
155
|
+
name: string
|
|
156
|
+
secret: string
|
|
157
|
+
tolerance?: number // timestamp tolerance in seconds
|
|
158
|
+
}>
|
|
159
|
+
|
|
160
|
+
/** Dispatcher configuration */
|
|
161
|
+
dispatcher?: WebhookDispatcherConfig
|
|
162
|
+
|
|
163
|
+
/** Base path for webhook endpoints */
|
|
164
|
+
basePath?: string // default: '/webhooks'
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT
|
package/dist/index.cjs
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/receive/SignatureValidator.ts
|
|
3
|
+
async function computeHmacSha256(payload, secret) {
|
|
4
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
5
|
+
const payloadBuffer = typeof payload === "string" ? new TextEncoder().encode(payload) : new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
6
|
+
const signature = await crypto.subtle.sign("HMAC", key, payloadBuffer);
|
|
7
|
+
return Buffer.from(signature).toString("hex");
|
|
8
|
+
}
|
|
9
|
+
async function computeHmacSha1(payload, secret) {
|
|
10
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-1" }, false, ["sign"]);
|
|
11
|
+
const payloadBuffer = typeof payload === "string" ? new TextEncoder().encode(payload) : new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
12
|
+
const signature = await crypto.subtle.sign("HMAC", key, payloadBuffer);
|
|
13
|
+
return Buffer.from(signature).toString("hex");
|
|
14
|
+
}
|
|
15
|
+
function timingSafeEqual(a, b) {
|
|
16
|
+
if (a.length !== b.length) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const aBytes = new TextEncoder().encode(a);
|
|
20
|
+
const bBytes = new TextEncoder().encode(b);
|
|
21
|
+
let result = 0;
|
|
22
|
+
for (let i = 0;i < aBytes.length; i++) {
|
|
23
|
+
result |= (aBytes[i] ?? 0) ^ (bBytes[i] ?? 0);
|
|
24
|
+
}
|
|
25
|
+
return result === 0;
|
|
26
|
+
}
|
|
27
|
+
function validateTimestamp(timestamp, tolerance = 300) {
|
|
28
|
+
const now = Math.floor(Date.now() / 1000);
|
|
29
|
+
return Math.abs(now - timestamp) <= tolerance;
|
|
30
|
+
}
|
|
31
|
+
function parseStripeSignature(header) {
|
|
32
|
+
const parts = header.split(",");
|
|
33
|
+
let timestamp;
|
|
34
|
+
const signatures = [];
|
|
35
|
+
for (const part of parts) {
|
|
36
|
+
const [key, value] = part.split("=");
|
|
37
|
+
if (key === "t" && value !== undefined) {
|
|
38
|
+
timestamp = parseInt(value, 10);
|
|
39
|
+
} else if (key === "v1" && value !== undefined) {
|
|
40
|
+
signatures.push(value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (timestamp === undefined || signatures.length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return { timestamp, signatures };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/providers/GenericProvider.ts
|
|
50
|
+
class GenericProvider {
|
|
51
|
+
name = "generic";
|
|
52
|
+
signatureHeader;
|
|
53
|
+
timestampHeader;
|
|
54
|
+
tolerance;
|
|
55
|
+
constructor(options = {}) {
|
|
56
|
+
this.signatureHeader = options.signatureHeader ?? "x-webhook-signature";
|
|
57
|
+
this.timestampHeader = options.timestampHeader ?? "x-webhook-timestamp";
|
|
58
|
+
this.tolerance = options.tolerance ?? 300;
|
|
59
|
+
}
|
|
60
|
+
async verify(payload, headers, secret) {
|
|
61
|
+
const signature = this.getHeader(headers, this.signatureHeader);
|
|
62
|
+
if (!signature) {
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
error: `Missing signature header: ${this.signatureHeader}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const timestampStr = this.getHeader(headers, this.timestampHeader);
|
|
69
|
+
if (timestampStr) {
|
|
70
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
71
|
+
if (isNaN(timestamp) || !validateTimestamp(timestamp, this.tolerance)) {
|
|
72
|
+
return {
|
|
73
|
+
valid: false,
|
|
74
|
+
error: "Timestamp validation failed"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const payloadStr = typeof payload === "string" ? payload : payload.toString("utf-8");
|
|
79
|
+
const expectedSignature = await computeHmacSha256(payloadStr, secret);
|
|
80
|
+
if (!timingSafeEqual(signature.toLowerCase(), expectedSignature.toLowerCase())) {
|
|
81
|
+
return {
|
|
82
|
+
valid: false,
|
|
83
|
+
error: "Signature verification failed"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(payloadStr);
|
|
88
|
+
return {
|
|
89
|
+
valid: true,
|
|
90
|
+
payload: parsed,
|
|
91
|
+
eventType: parsed.type ?? parsed.event ?? parsed.eventType,
|
|
92
|
+
webhookId: parsed.id ?? parsed.webhookId
|
|
93
|
+
};
|
|
94
|
+
} catch {
|
|
95
|
+
return {
|
|
96
|
+
valid: true,
|
|
97
|
+
payload: payloadStr
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
getHeader(headers, name) {
|
|
102
|
+
const value = headers[name] ?? headers[name.toLowerCase()];
|
|
103
|
+
return Array.isArray(value) ? value[0] : value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/providers/GitHubProvider.ts
|
|
108
|
+
class GitHubProvider {
|
|
109
|
+
name = "github";
|
|
110
|
+
async verify(payload, headers, secret) {
|
|
111
|
+
const signature = this.getHeader(headers, "x-hub-signature-256");
|
|
112
|
+
if (!signature) {
|
|
113
|
+
return {
|
|
114
|
+
valid: false,
|
|
115
|
+
error: "Missing X-Hub-Signature-256 header"
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (!signature.startsWith("sha256=")) {
|
|
119
|
+
return {
|
|
120
|
+
valid: false,
|
|
121
|
+
error: "Invalid signature format (expected sha256=...)"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const signatureValue = signature.slice(7);
|
|
125
|
+
const payloadStr = typeof payload === "string" ? payload : payload.toString("utf-8");
|
|
126
|
+
const expectedSignature = await computeHmacSha256(payloadStr, secret);
|
|
127
|
+
if (!timingSafeEqual(signatureValue.toLowerCase(), expectedSignature.toLowerCase())) {
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
error: "Signature verification failed"
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const event = JSON.parse(payloadStr);
|
|
135
|
+
const eventType = this.getHeader(headers, "x-github-event");
|
|
136
|
+
const deliveryId = this.getHeader(headers, "x-github-delivery");
|
|
137
|
+
return {
|
|
138
|
+
valid: true,
|
|
139
|
+
payload: event,
|
|
140
|
+
eventType: eventType ?? undefined,
|
|
141
|
+
webhookId: deliveryId ?? undefined
|
|
142
|
+
};
|
|
143
|
+
} catch {
|
|
144
|
+
return {
|
|
145
|
+
valid: false,
|
|
146
|
+
error: "Failed to parse webhook payload"
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
parseEventType(payload) {
|
|
151
|
+
if (typeof payload === "object" && payload !== null && "action" in payload) {
|
|
152
|
+
return payload.action;
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
getHeader(headers, name) {
|
|
157
|
+
const value = headers[name] ?? headers[name.toLowerCase()];
|
|
158
|
+
return Array.isArray(value) ? value[0] : value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/providers/StripeProvider.ts
|
|
163
|
+
class StripeProvider {
|
|
164
|
+
name = "stripe";
|
|
165
|
+
tolerance;
|
|
166
|
+
constructor(options = {}) {
|
|
167
|
+
this.tolerance = options.tolerance ?? 300;
|
|
168
|
+
}
|
|
169
|
+
async verify(payload, headers, secret) {
|
|
170
|
+
const signatureHeader = this.getHeader(headers, "stripe-signature");
|
|
171
|
+
if (!signatureHeader) {
|
|
172
|
+
return {
|
|
173
|
+
valid: false,
|
|
174
|
+
error: "Missing Stripe-Signature header"
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const parsed = parseStripeSignature(signatureHeader);
|
|
178
|
+
if (!parsed) {
|
|
179
|
+
return {
|
|
180
|
+
valid: false,
|
|
181
|
+
error: "Invalid Stripe-Signature header format"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const { timestamp, signatures } = parsed;
|
|
185
|
+
if (!validateTimestamp(timestamp, this.tolerance)) {
|
|
186
|
+
return {
|
|
187
|
+
valid: false,
|
|
188
|
+
error: `Timestamp outside tolerance window (${this.tolerance}s)`
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const payloadStr = typeof payload === "string" ? payload : payload.toString("utf-8");
|
|
192
|
+
const signedPayload = `${timestamp}.${payloadStr}`;
|
|
193
|
+
const expectedSignature = await computeHmacSha256(signedPayload, secret);
|
|
194
|
+
const signatureValid = signatures.some((sig) => timingSafeEqual(sig.toLowerCase(), expectedSignature.toLowerCase()));
|
|
195
|
+
if (!signatureValid) {
|
|
196
|
+
return {
|
|
197
|
+
valid: false,
|
|
198
|
+
error: "Signature verification failed"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const event = JSON.parse(payloadStr);
|
|
203
|
+
return {
|
|
204
|
+
valid: true,
|
|
205
|
+
payload: event,
|
|
206
|
+
eventType: event.type,
|
|
207
|
+
webhookId: event.id
|
|
208
|
+
};
|
|
209
|
+
} catch {
|
|
210
|
+
return {
|
|
211
|
+
valid: false,
|
|
212
|
+
error: "Failed to parse webhook payload"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
parseEventType(payload) {
|
|
217
|
+
if (typeof payload === "object" && payload !== null && "type" in payload) {
|
|
218
|
+
return payload.type;
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
getHeader(headers, name) {
|
|
223
|
+
const value = headers[name] ?? headers[name.toLowerCase()];
|
|
224
|
+
return Array.isArray(value) ? value[0] : value;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/receive/WebhookReceiver.ts
|
|
229
|
+
class WebhookReceiver {
|
|
230
|
+
providers = new Map;
|
|
231
|
+
handlers = new Map;
|
|
232
|
+
globalHandlers = new Map;
|
|
233
|
+
constructor() {
|
|
234
|
+
this.registerProviderType("generic", GenericProvider);
|
|
235
|
+
this.registerProviderType("stripe", StripeProvider);
|
|
236
|
+
this.registerProviderType("github", GitHubProvider);
|
|
237
|
+
}
|
|
238
|
+
providerTypes = new Map;
|
|
239
|
+
registerProviderType(name, ProviderCls) {
|
|
240
|
+
this.providerTypes.set(name, ProviderCls);
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
registerProvider(name, secret, options) {
|
|
244
|
+
const type = options?.type ?? name;
|
|
245
|
+
const ProviderClass = this.providerTypes.get(type);
|
|
246
|
+
if (!ProviderClass) {
|
|
247
|
+
throw new Error(`Unknown provider type: ${type}`);
|
|
248
|
+
}
|
|
249
|
+
const provider = new ProviderClass({ tolerance: options?.tolerance });
|
|
250
|
+
this.providers.set(name, { provider, secret });
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
on(providerName, eventType, handler) {
|
|
254
|
+
if (!this.handlers.has(providerName)) {
|
|
255
|
+
this.handlers.set(providerName, new Map);
|
|
256
|
+
}
|
|
257
|
+
const providerHandlers = this.handlers.get(providerName);
|
|
258
|
+
if (!providerHandlers.has(eventType)) {
|
|
259
|
+
providerHandlers.set(eventType, []);
|
|
260
|
+
}
|
|
261
|
+
providerHandlers.get(eventType).push(handler);
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
onAll(providerName, handler) {
|
|
265
|
+
if (!this.globalHandlers.has(providerName)) {
|
|
266
|
+
this.globalHandlers.set(providerName, []);
|
|
267
|
+
}
|
|
268
|
+
this.globalHandlers.get(providerName).push(handler);
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
async handle(providerName, body, headers) {
|
|
272
|
+
const config = this.providers.get(providerName);
|
|
273
|
+
if (!config) {
|
|
274
|
+
return {
|
|
275
|
+
valid: false,
|
|
276
|
+
error: `Provider not registered: ${providerName}`,
|
|
277
|
+
handled: false
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const { provider, secret } = config;
|
|
281
|
+
const result = await provider.verify(body, headers, secret);
|
|
282
|
+
if (!result.valid) {
|
|
283
|
+
return { ...result, handled: false };
|
|
284
|
+
}
|
|
285
|
+
const event = {
|
|
286
|
+
provider: providerName,
|
|
287
|
+
type: result.eventType ?? "unknown",
|
|
288
|
+
payload: result.payload,
|
|
289
|
+
headers,
|
|
290
|
+
rawBody: typeof body === "string" ? body : body.toString("utf-8"),
|
|
291
|
+
receivedAt: new Date,
|
|
292
|
+
id: result.webhookId
|
|
293
|
+
};
|
|
294
|
+
let handled = false;
|
|
295
|
+
const providerHandlers = this.handlers.get(providerName);
|
|
296
|
+
if (providerHandlers) {
|
|
297
|
+
const eventHandlers = providerHandlers.get(event.type);
|
|
298
|
+
if (eventHandlers) {
|
|
299
|
+
for (const handler of eventHandlers) {
|
|
300
|
+
await handler(event);
|
|
301
|
+
handled = true;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const globalHandlers = this.globalHandlers.get(providerName);
|
|
306
|
+
if (globalHandlers) {
|
|
307
|
+
for (const handler of globalHandlers) {
|
|
308
|
+
await handler(event);
|
|
309
|
+
handled = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return { ...result, handled };
|
|
313
|
+
}
|
|
314
|
+
async verify(providerName, body, headers) {
|
|
315
|
+
const config = this.providers.get(providerName);
|
|
316
|
+
if (!config) {
|
|
317
|
+
return {
|
|
318
|
+
valid: false,
|
|
319
|
+
error: `Provider not registered: ${providerName}`
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
return config.provider.verify(body, headers, config.secret);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/send/WebhookDispatcher.ts
|
|
327
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
328
|
+
maxAttempts: 3,
|
|
329
|
+
initialDelay: 1000,
|
|
330
|
+
backoffMultiplier: 2,
|
|
331
|
+
maxDelay: 300000,
|
|
332
|
+
retryableStatuses: [408, 429, 500, 502, 503, 504]
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
class WebhookDispatcher {
|
|
336
|
+
secret;
|
|
337
|
+
retryConfig;
|
|
338
|
+
timeout;
|
|
339
|
+
userAgent;
|
|
340
|
+
constructor(config) {
|
|
341
|
+
this.secret = config.secret;
|
|
342
|
+
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retry };
|
|
343
|
+
this.timeout = config.timeout ?? 30000;
|
|
344
|
+
this.userAgent = config.userAgent ?? "Gravito-Echo/1.0";
|
|
345
|
+
}
|
|
346
|
+
async dispatch(payload) {
|
|
347
|
+
let lastResult = null;
|
|
348
|
+
for (let attempt = 1;attempt <= this.retryConfig.maxAttempts; attempt++) {
|
|
349
|
+
const result = await this.attemptDelivery(payload, attempt);
|
|
350
|
+
lastResult = result;
|
|
351
|
+
if (result.success) {
|
|
352
|
+
return result;
|
|
353
|
+
}
|
|
354
|
+
if (attempt < this.retryConfig.maxAttempts) {
|
|
355
|
+
const shouldRetry = this.shouldRetry(result);
|
|
356
|
+
if (shouldRetry) {
|
|
357
|
+
const delay = this.calculateDelay(attempt);
|
|
358
|
+
await this.sleep(delay);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
return lastResult;
|
|
365
|
+
}
|
|
366
|
+
async attemptDelivery(payload, attempt) {
|
|
367
|
+
const startTime = Date.now();
|
|
368
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
369
|
+
const webhookId = payload.id ?? crypto.randomUUID();
|
|
370
|
+
try {
|
|
371
|
+
const body = JSON.stringify({
|
|
372
|
+
id: webhookId,
|
|
373
|
+
type: payload.event,
|
|
374
|
+
timestamp,
|
|
375
|
+
data: payload.data
|
|
376
|
+
});
|
|
377
|
+
const signedPayload = `${timestamp}.${body}`;
|
|
378
|
+
const signature = await computeHmacSha256(signedPayload, this.secret);
|
|
379
|
+
const controller = new AbortController;
|
|
380
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
381
|
+
try {
|
|
382
|
+
const response = await fetch(payload.url, {
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers: {
|
|
385
|
+
"Content-Type": "application/json",
|
|
386
|
+
"User-Agent": this.userAgent,
|
|
387
|
+
"X-Webhook-ID": webhookId,
|
|
388
|
+
"X-Webhook-Timestamp": String(timestamp),
|
|
389
|
+
"X-Webhook-Signature": `t=${timestamp},v1=${signature}`
|
|
390
|
+
},
|
|
391
|
+
body,
|
|
392
|
+
signal: controller.signal
|
|
393
|
+
});
|
|
394
|
+
clearTimeout(timeoutId);
|
|
395
|
+
const duration = Date.now() - startTime;
|
|
396
|
+
const responseBody = await response.text();
|
|
397
|
+
return {
|
|
398
|
+
success: response.ok,
|
|
399
|
+
statusCode: response.status,
|
|
400
|
+
body: responseBody,
|
|
401
|
+
attempt,
|
|
402
|
+
duration,
|
|
403
|
+
deliveredAt: new Date,
|
|
404
|
+
error: response.ok ? undefined : `HTTP ${response.status}`
|
|
405
|
+
};
|
|
406
|
+
} finally {
|
|
407
|
+
clearTimeout(timeoutId);
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
const duration = Date.now() - startTime;
|
|
411
|
+
return {
|
|
412
|
+
success: false,
|
|
413
|
+
attempt,
|
|
414
|
+
duration,
|
|
415
|
+
deliveredAt: new Date,
|
|
416
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
shouldRetry(result) {
|
|
421
|
+
if (!result.statusCode) {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
return this.retryConfig.retryableStatuses.includes(result.statusCode);
|
|
425
|
+
}
|
|
426
|
+
calculateDelay(attempt) {
|
|
427
|
+
const delay = this.retryConfig.initialDelay * this.retryConfig.backoffMultiplier ** (attempt - 1);
|
|
428
|
+
return Math.min(delay, this.retryConfig.maxDelay);
|
|
429
|
+
}
|
|
430
|
+
sleep(ms) {
|
|
431
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/OrbitEcho.ts
|
|
436
|
+
class OrbitEcho {
|
|
437
|
+
static config = { singleton: true };
|
|
438
|
+
receiver;
|
|
439
|
+
dispatcher;
|
|
440
|
+
echoConfig;
|
|
441
|
+
constructor(config = {}) {
|
|
442
|
+
this.echoConfig = config;
|
|
443
|
+
this.receiver = new WebhookReceiver;
|
|
444
|
+
if (config.providers) {
|
|
445
|
+
for (const [name, providerConfig] of Object.entries(config.providers)) {
|
|
446
|
+
this.receiver.registerProvider(name, providerConfig.secret, {
|
|
447
|
+
type: providerConfig.name,
|
|
448
|
+
tolerance: providerConfig.tolerance
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (config.dispatcher) {
|
|
453
|
+
this.dispatcher = new WebhookDispatcher(config.dispatcher);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
install(core) {
|
|
457
|
+
core.container.bindSingleton("echo", this);
|
|
458
|
+
core.container.bindSingleton("echo.receiver", this.receiver);
|
|
459
|
+
if (this.dispatcher) {
|
|
460
|
+
core.container.bindSingleton("echo.dispatcher", this.dispatcher);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
getReceiver() {
|
|
464
|
+
return this.receiver;
|
|
465
|
+
}
|
|
466
|
+
getDispatcher() {
|
|
467
|
+
return this.dispatcher;
|
|
468
|
+
}
|
|
469
|
+
getConfig() {
|
|
470
|
+
return this.echoConfig;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
export {
|
|
474
|
+
validateTimestamp,
|
|
475
|
+
timingSafeEqual,
|
|
476
|
+
parseStripeSignature,
|
|
477
|
+
computeHmacSha256,
|
|
478
|
+
computeHmacSha1,
|
|
479
|
+
WebhookReceiver,
|
|
480
|
+
WebhookDispatcher,
|
|
481
|
+
StripeProvider,
|
|
482
|
+
OrbitEcho,
|
|
483
|
+
GitHubProvider,
|
|
484
|
+
GenericProvider
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
//# debugId=20EC60D051D827F464756E2164756E21
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/receive/SignatureValidator.ts", "../src/providers/GenericProvider.ts", "../src/providers/GitHubProvider.ts", "../src/providers/StripeProvider.ts", "../src/receive/WebhookReceiver.ts", "../src/send/WebhookDispatcher.ts", "../src/OrbitEcho.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @fileoverview Signature validation utilities\n *\n * Provides HMAC-based signature verification for webhook payloads.\n *\n * @module @gravito/echo/receive\n */\n\n/**\n * Compute HMAC-SHA256 signature\n */\nexport async function computeHmacSha256(payload: string | Buffer, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n\n const payloadBuffer =\n typeof payload === 'string'\n ? new TextEncoder().encode(payload)\n : new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength)\n\n const signature = await crypto.subtle.sign('HMAC', key, payloadBuffer as BufferSource)\n return Buffer.from(signature).toString('hex')\n}\n\n/**\n * Compute HMAC-SHA1 signature (for legacy providers)\n */\nexport async function computeHmacSha1(payload: string | Buffer, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-1' },\n false,\n ['sign']\n )\n\n const payloadBuffer =\n typeof payload === 'string'\n ? new TextEncoder().encode(payload)\n : new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength)\n\n const signature = await crypto.subtle.sign('HMAC', key, payloadBuffer as BufferSource)\n return Buffer.from(signature).toString('hex')\n}\n\n/**\n * Timing-safe string comparison to prevent timing attacks\n */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false\n }\n\n const aBytes = new TextEncoder().encode(a)\n const bBytes = new TextEncoder().encode(b)\n\n let result = 0\n for (let i = 0; i < aBytes.length; i++) {\n result |= (aBytes[i] ?? 0) ^ (bBytes[i] ?? 0)\n }\n\n return result === 0\n}\n\n/**\n * Validate timestamp is within tolerance\n *\n * @param timestamp - Unix timestamp in seconds\n * @param tolerance - Tolerance in seconds (default: 300 = 5 minutes)\n */\nexport function validateTimestamp(timestamp: number, tolerance = 300): boolean {\n const now = Math.floor(Date.now() / 1000)\n return Math.abs(now - timestamp) <= tolerance\n}\n\n/**\n * Parse Stripe-style signature header\n * Format: t=timestamp,v1=signature,v1=signature2\n */\nexport function parseStripeSignature(\n header: string\n): { timestamp: number; signatures: string[] } | null {\n const parts = header.split(',')\n let timestamp: number | undefined\n const signatures: string[] = []\n\n for (const part of parts) {\n const [key, value] = part.split('=')\n if (key === 't' && value !== undefined) {\n timestamp = parseInt(value, 10)\n } else if (key === 'v1' && value !== undefined) {\n signatures.push(value)\n }\n }\n\n if (timestamp === undefined || signatures.length === 0) {\n return null\n }\n\n return { timestamp, signatures }\n}\n",
|
|
6
|
+
"/**\n * @fileoverview Generic webhook provider\n *\n * Simple HMAC-SHA256 signature verification.\n *\n * @module @gravito/echo/providers\n */\n\nimport {\n computeHmacSha256,\n timingSafeEqual,\n validateTimestamp,\n} from '../receive/SignatureValidator'\nimport type { WebhookProvider, WebhookVerificationResult } from '../types'\n\n/**\n * Generic webhook provider using HMAC-SHA256\n *\n * Expected headers:\n * - X-Webhook-Signature: HMAC-SHA256 hex signature\n * - X-Webhook-Timestamp: Unix timestamp (optional)\n *\n * @example\n * ```typescript\n * const provider = new GenericProvider()\n * const result = await provider.verify(body, headers, secret)\n * ```\n */\nexport class GenericProvider implements WebhookProvider {\n readonly name = 'generic'\n\n private signatureHeader: string\n private timestampHeader: string\n private tolerance: number\n\n constructor(\n options: {\n signatureHeader?: string\n timestampHeader?: string\n tolerance?: number\n } = {}\n ) {\n this.signatureHeader = options.signatureHeader ?? 'x-webhook-signature'\n this.timestampHeader = options.timestampHeader ?? 'x-webhook-timestamp'\n this.tolerance = options.tolerance ?? 300\n }\n\n async verify(\n payload: string | Buffer,\n headers: Record<string, string | string[] | undefined>,\n secret: string\n ): Promise<WebhookVerificationResult> {\n // Get signature from headers\n const signature = this.getHeader(headers, this.signatureHeader)\n if (!signature) {\n return {\n valid: false,\n error: `Missing signature header: ${this.signatureHeader}`,\n }\n }\n\n // Validate timestamp if present\n const timestampStr = this.getHeader(headers, this.timestampHeader)\n if (timestampStr) {\n const timestamp = parseInt(timestampStr, 10)\n if (isNaN(timestamp) || !validateTimestamp(timestamp, this.tolerance)) {\n return {\n valid: false,\n error: 'Timestamp validation failed',\n }\n }\n }\n\n // Compute expected signature\n const payloadStr = typeof payload === 'string' ? payload : payload.toString('utf-8')\n const expectedSignature = await computeHmacSha256(payloadStr, secret)\n\n // Compare signatures\n if (!timingSafeEqual(signature.toLowerCase(), expectedSignature.toLowerCase())) {\n return {\n valid: false,\n error: 'Signature verification failed',\n }\n }\n\n // Parse payload\n try {\n const parsed = JSON.parse(payloadStr)\n return {\n valid: true,\n payload: parsed,\n eventType: parsed.type ?? parsed.event ?? parsed.eventType,\n webhookId: parsed.id ?? parsed.webhookId,\n }\n } catch {\n return {\n valid: true,\n payload: payloadStr,\n }\n }\n }\n\n private getHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string\n ): string | undefined {\n const value = headers[name] ?? headers[name.toLowerCase()]\n return Array.isArray(value) ? value[0] : value\n }\n}\n",
|
|
7
|
+
"/**\n * @fileoverview GitHub webhook provider\n *\n * Implements GitHub's webhook signature verification.\n * @see https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries\n *\n * @module @gravito/echo/providers\n */\n\nimport { computeHmacSha256, timingSafeEqual } from '../receive/SignatureValidator'\nimport type { WebhookProvider, WebhookVerificationResult } from '../types'\n\n/**\n * GitHub webhook provider\n *\n * Verifies GitHub webhook signatures using the X-Hub-Signature-256 header.\n *\n * @example\n * ```typescript\n * const provider = new GitHubProvider()\n * const result = await provider.verify(body, headers, process.env.GITHUB_WEBHOOK_SECRET)\n * ```\n */\nexport class GitHubProvider implements WebhookProvider {\n readonly name = 'github'\n\n async verify(\n payload: string | Buffer,\n headers: Record<string, string | string[] | undefined>,\n secret: string\n ): Promise<WebhookVerificationResult> {\n // GitHub sends signature in X-Hub-Signature-256 header\n const signature = this.getHeader(headers, 'x-hub-signature-256')\n if (!signature) {\n return {\n valid: false,\n error: 'Missing X-Hub-Signature-256 header',\n }\n }\n\n // Signature format: sha256=<hex>\n if (!signature.startsWith('sha256=')) {\n return {\n valid: false,\n error: 'Invalid signature format (expected sha256=...)',\n }\n }\n\n const signatureValue = signature.slice(7) // Remove 'sha256=' prefix\n\n // Compute expected signature\n const payloadStr = typeof payload === 'string' ? payload : payload.toString('utf-8')\n const expectedSignature = await computeHmacSha256(payloadStr, secret)\n\n // Compare signatures\n if (!timingSafeEqual(signatureValue.toLowerCase(), expectedSignature.toLowerCase())) {\n return {\n valid: false,\n error: 'Signature verification failed',\n }\n }\n\n // Parse payload\n try {\n const event = JSON.parse(payloadStr)\n const eventType = this.getHeader(headers, 'x-github-event')\n const deliveryId = this.getHeader(headers, 'x-github-delivery')\n\n return {\n valid: true,\n payload: event,\n eventType: eventType ?? undefined,\n webhookId: deliveryId ?? undefined,\n }\n } catch {\n return {\n valid: false,\n error: 'Failed to parse webhook payload',\n }\n }\n }\n\n parseEventType(payload: unknown): string | undefined {\n if (typeof payload === 'object' && payload !== null && 'action' in payload) {\n return (payload as { action: string }).action\n }\n return undefined\n }\n\n private getHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string\n ): string | undefined {\n const value = headers[name] ?? headers[name.toLowerCase()]\n return Array.isArray(value) ? value[0] : value\n }\n}\n",
|
|
8
|
+
"/**\n * @fileoverview Stripe webhook provider\n *\n * Implements Stripe's webhook signature verification.\n * @see https://stripe.com/docs/webhooks/signatures\n *\n * @module @gravito/echo/providers\n */\n\nimport {\n computeHmacSha256,\n parseStripeSignature,\n timingSafeEqual,\n validateTimestamp,\n} from '../receive/SignatureValidator'\nimport type { WebhookProvider, WebhookVerificationResult } from '../types'\n\n/**\n * Stripe webhook provider\n *\n * Verifies Stripe webhook signatures using their standard format.\n *\n * @example\n * ```typescript\n * const provider = new StripeProvider()\n * const result = await provider.verify(body, headers, process.env.STRIPE_WEBHOOK_SECRET)\n * ```\n */\nexport class StripeProvider implements WebhookProvider {\n readonly name = 'stripe'\n\n private tolerance: number\n\n constructor(options: { tolerance?: number } = {}) {\n this.tolerance = options.tolerance ?? 300\n }\n\n async verify(\n payload: string | Buffer,\n headers: Record<string, string | string[] | undefined>,\n secret: string\n ): Promise<WebhookVerificationResult> {\n // Get signature header\n const signatureHeader = this.getHeader(headers, 'stripe-signature')\n if (!signatureHeader) {\n return {\n valid: false,\n error: 'Missing Stripe-Signature header',\n }\n }\n\n // Parse signature header\n const parsed = parseStripeSignature(signatureHeader)\n if (!parsed) {\n return {\n valid: false,\n error: 'Invalid Stripe-Signature header format',\n }\n }\n\n const { timestamp, signatures } = parsed\n\n // Validate timestamp\n if (!validateTimestamp(timestamp, this.tolerance)) {\n return {\n valid: false,\n error: `Timestamp outside tolerance window (${this.tolerance}s)`,\n }\n }\n\n // Compute expected signature\n const payloadStr = typeof payload === 'string' ? payload : payload.toString('utf-8')\n const signedPayload = `${timestamp}.${payloadStr}`\n const expectedSignature = await computeHmacSha256(signedPayload, secret)\n\n // Check if any signature matches\n const signatureValid = signatures.some((sig) =>\n timingSafeEqual(sig.toLowerCase(), expectedSignature.toLowerCase())\n )\n\n if (!signatureValid) {\n return {\n valid: false,\n error: 'Signature verification failed',\n }\n }\n\n // Parse payload\n try {\n const event = JSON.parse(payloadStr)\n return {\n valid: true,\n payload: event,\n eventType: event.type,\n webhookId: event.id,\n }\n } catch {\n return {\n valid: false,\n error: 'Failed to parse webhook payload',\n }\n }\n }\n\n parseEventType(payload: unknown): string | undefined {\n if (typeof payload === 'object' && payload !== null && 'type' in payload) {\n return (payload as { type: string }).type\n }\n return undefined\n }\n\n private getHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string\n ): string | undefined {\n const value = headers[name] ?? headers[name.toLowerCase()]\n return Array.isArray(value) ? value[0] : value\n }\n}\n",
|
|
9
|
+
"/**\n * @fileoverview Webhook Receiver\n *\n * Handles incoming webhooks with signature verification.\n *\n * @module @gravito/echo/receive\n */\n\nimport { GenericProvider } from '../providers/GenericProvider'\nimport { GitHubProvider } from '../providers/GitHubProvider'\nimport { StripeProvider } from '../providers/StripeProvider'\nimport type {\n WebhookEvent,\n WebhookHandler,\n WebhookProvider,\n WebhookVerificationResult,\n} from '../types'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ProviderClass = new (options?: any) => WebhookProvider\n\n/**\n * Webhook Receiver\n *\n * Manages webhook providers and routes incoming webhooks to handlers.\n *\n * @example\n * ```typescript\n * const receiver = new WebhookReceiver()\n *\n * // Register provider\n * receiver.registerProvider('stripe', process.env.STRIPE_WEBHOOK_SECRET!)\n *\n * // Register handler\n * receiver.on('stripe', 'payment_intent.succeeded', async (event) => {\n * console.log('Payment received:', event.payload)\n * })\n *\n * // Handle incoming webhook\n * const result = await receiver.handle('stripe', body, headers)\n * ```\n */\nexport class WebhookReceiver {\n private providers = new Map<string, { provider: WebhookProvider; secret: string }>()\n private handlers = new Map<string, Map<string, WebhookHandler[]>>()\n private globalHandlers = new Map<string, WebhookHandler[]>()\n\n constructor() {\n // Register built-in providers\n this.registerProviderType('generic', GenericProvider as ProviderClass)\n this.registerProviderType('stripe', StripeProvider as ProviderClass)\n this.registerProviderType('github', GitHubProvider as ProviderClass)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private providerTypes = new Map<string, ProviderClass>()\n\n /**\n * Register a custom provider type\n */\n registerProviderType(name: string, ProviderCls: ProviderClass): this {\n this.providerTypes.set(name, ProviderCls)\n return this\n }\n\n /**\n * Register a provider with its secret\n */\n registerProvider(\n name: string,\n secret: string,\n options?: { type?: string; tolerance?: number }\n ): this {\n const type = options?.type ?? name\n const ProviderClass = this.providerTypes.get(type)\n\n if (!ProviderClass) {\n throw new Error(`Unknown provider type: ${type}`)\n }\n\n const provider = new ProviderClass({ tolerance: options?.tolerance })\n this.providers.set(name, { provider, secret })\n return this\n }\n\n /**\n * Register an event handler\n */\n on<T = unknown>(providerName: string, eventType: string, handler: WebhookHandler<T>): this {\n if (!this.handlers.has(providerName)) {\n this.handlers.set(providerName, new Map())\n }\n\n const providerHandlers = this.handlers.get(providerName)!\n if (!providerHandlers.has(eventType)) {\n providerHandlers.set(eventType, [])\n }\n\n providerHandlers.get(eventType)!.push(handler as WebhookHandler)\n return this\n }\n\n /**\n * Register a handler for all events from a provider\n */\n onAll<T = unknown>(providerName: string, handler: WebhookHandler<T>): this {\n if (!this.globalHandlers.has(providerName)) {\n this.globalHandlers.set(providerName, [])\n }\n\n this.globalHandlers.get(providerName)!.push(handler as WebhookHandler)\n return this\n }\n\n /**\n * Handle an incoming webhook\n */\n async handle(\n providerName: string,\n body: string | Buffer,\n headers: Record<string, string | string[] | undefined>\n ): Promise<WebhookVerificationResult & { handled: boolean }> {\n const config = this.providers.get(providerName)\n if (!config) {\n return {\n valid: false,\n error: `Provider not registered: ${providerName}`,\n handled: false,\n }\n }\n\n const { provider, secret } = config\n\n // Verify webhook\n const result = await provider.verify(body, headers, secret)\n if (!result.valid) {\n return { ...result, handled: false }\n }\n\n // Create event object\n const event: WebhookEvent = {\n provider: providerName,\n type: result.eventType ?? 'unknown',\n payload: result.payload,\n headers,\n rawBody: typeof body === 'string' ? body : body.toString('utf-8'),\n receivedAt: new Date(),\n id: result.webhookId,\n }\n\n // Call handlers\n let handled = false\n\n // Call event-specific handlers\n const providerHandlers = this.handlers.get(providerName)\n if (providerHandlers) {\n const eventHandlers = providerHandlers.get(event.type)\n if (eventHandlers) {\n for (const handler of eventHandlers) {\n await handler(event)\n handled = true\n }\n }\n }\n\n // Call global handlers\n const globalHandlers = this.globalHandlers.get(providerName)\n if (globalHandlers) {\n for (const handler of globalHandlers) {\n await handler(event)\n handled = true\n }\n }\n\n return { ...result, handled }\n }\n\n /**\n * Verify a webhook without handling\n */\n async verify(\n providerName: string,\n body: string | Buffer,\n headers: Record<string, string | string[] | undefined>\n ): Promise<WebhookVerificationResult> {\n const config = this.providers.get(providerName)\n if (!config) {\n return {\n valid: false,\n error: `Provider not registered: ${providerName}`,\n }\n }\n\n return config.provider.verify(body, headers, config.secret)\n }\n}\n",
|
|
10
|
+
"/**\n * @fileoverview Webhook Dispatcher\n *\n * Reliably sends webhooks to external services with retry support.\n *\n * @module @gravito/echo/send\n */\n\nimport { computeHmacSha256 } from '../receive/SignatureValidator'\nimport type {\n RetryConfig,\n WebhookDeliveryResult,\n WebhookDispatcherConfig,\n WebhookPayload,\n} from '../types'\n\n/**\n * Default retry configuration\n */\nconst DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {\n maxAttempts: 3,\n initialDelay: 1000,\n backoffMultiplier: 2,\n maxDelay: 300000, // 5 minutes\n retryableStatuses: [408, 429, 500, 502, 503, 504],\n}\n\n/**\n * Webhook Dispatcher\n *\n * Sends webhooks with signature and retry support.\n *\n * @example\n * ```typescript\n * const dispatcher = new WebhookDispatcher({\n * secret: 'my-webhook-secret',\n * retry: { maxAttempts: 5 }\n * })\n *\n * const result = await dispatcher.dispatch({\n * url: 'https://example.com/webhook',\n * event: 'order.created',\n * data: { orderId: 123 }\n * })\n * ```\n */\nexport class WebhookDispatcher {\n private secret: string\n private retryConfig: Required<RetryConfig>\n private timeout: number\n private userAgent: string\n\n constructor(config: WebhookDispatcherConfig) {\n this.secret = config.secret\n this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retry }\n this.timeout = config.timeout ?? 30000\n this.userAgent = config.userAgent ?? 'Gravito-Echo/1.0'\n }\n\n /**\n * Dispatch a webhook with retries\n */\n async dispatch<T = unknown>(payload: WebhookPayload<T>): Promise<WebhookDeliveryResult> {\n let lastResult: WebhookDeliveryResult | null = null\n\n for (let attempt = 1; attempt <= this.retryConfig.maxAttempts; attempt++) {\n const result = await this.attemptDelivery(payload, attempt)\n lastResult = result\n\n if (result.success) {\n return result\n }\n\n // Check if we should retry\n if (attempt < this.retryConfig.maxAttempts) {\n const shouldRetry = this.shouldRetry(result)\n if (shouldRetry) {\n const delay = this.calculateDelay(attempt)\n await this.sleep(delay)\n continue\n }\n }\n\n // Don't retry if status is not retryable\n return result\n }\n\n return lastResult!\n }\n\n /**\n * Attempt a single delivery\n */\n private async attemptDelivery<T = unknown>(\n payload: WebhookPayload<T>,\n attempt: number\n ): Promise<WebhookDeliveryResult> {\n const startTime = Date.now()\n const timestamp = Math.floor(Date.now() / 1000)\n const webhookId = payload.id ?? crypto.randomUUID()\n\n try {\n // Build request body\n const body = JSON.stringify({\n id: webhookId,\n type: payload.event,\n timestamp,\n data: payload.data,\n })\n\n // Compute signature\n const signedPayload = `${timestamp}.${body}`\n const signature = await computeHmacSha256(signedPayload, this.secret)\n\n // Create abort controller for timeout\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.timeout)\n\n try {\n const response = await fetch(payload.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': this.userAgent,\n 'X-Webhook-ID': webhookId,\n 'X-Webhook-Timestamp': String(timestamp),\n 'X-Webhook-Signature': `t=${timestamp},v1=${signature}`,\n },\n body,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n const duration = Date.now() - startTime\n const responseBody = await response.text()\n\n return {\n success: response.ok,\n statusCode: response.status,\n body: responseBody,\n attempt,\n duration,\n deliveredAt: new Date(),\n error: response.ok ? undefined : `HTTP ${response.status}`,\n }\n } finally {\n clearTimeout(timeoutId)\n }\n } catch (error) {\n const duration = Date.now() - startTime\n\n return {\n success: false,\n attempt,\n duration,\n deliveredAt: new Date(),\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n }\n }\n\n /**\n * Check if we should retry based on result\n */\n private shouldRetry(result: WebhookDeliveryResult): boolean {\n if (!result.statusCode) {\n // Network error, retry\n return true\n }\n\n return this.retryConfig.retryableStatuses.includes(result.statusCode)\n }\n\n /**\n * Calculate delay for exponential backoff\n */\n private calculateDelay(attempt: number): number {\n const delay =\n this.retryConfig.initialDelay * this.retryConfig.backoffMultiplier ** (attempt - 1)\n\n return Math.min(delay, this.retryConfig.maxDelay)\n }\n\n /**\n * Sleep helper\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n }\n}\n",
|
|
11
|
+
"/**\n * @fileoverview OrbitEcho Module\n *\n * Gravito integration for webhook handling.\n *\n * @module @gravito/echo\n */\n\nimport { WebhookReceiver } from './receive/WebhookReceiver'\nimport { WebhookDispatcher } from './send/WebhookDispatcher'\nimport type { EchoConfig } from './types'\n\n/**\n * Simple module interface for PlanetCore integration\n */\ninterface ModuleConfig {\n singleton?: boolean\n}\n\n/**\n * Minimal ServiceProvider interface\n */\ninterface ServiceProvider {\n register?(): void | Promise<void>\n boot?(): void | Promise<void>\n}\n\n/**\n * Minimal PlanetCore interface\n */\ninterface PlanetCore {\n container: {\n bindSingleton<T>(key: string, value: T): void\n }\n hooks: {\n addAction(hook: string, callback: () => void | Promise<void>): void\n }\n adapter: {\n use(middleware: unknown): void\n }\n router: {\n post(path: string, handler: unknown): void\n }\n}\n\n/**\n * OrbitEcho - Gravito Webhook Module\n *\n * Provides secure webhook receiving and reliable webhook sending.\n *\n * @example\n * ```typescript\n * const core = new PlanetCore()\n *\n * core.install(new OrbitEcho({\n * providers: {\n * stripe: { name: 'stripe', secret: process.env.STRIPE_WEBHOOK_SECRET! },\n * github: { name: 'github', secret: process.env.GITHUB_WEBHOOK_SECRET! }\n * },\n * dispatcher: {\n * secret: process.env.OUTGOING_WEBHOOK_SECRET!\n * }\n * }))\n *\n * // Get receiver to add handlers\n * const receiver = core.container.make<WebhookReceiver>('echo.receiver')\n * receiver.on('stripe', 'payment_intent.succeeded', async (event) => {\n * console.log('Payment received:', event.payload)\n * })\n * ```\n */\nexport class OrbitEcho {\n static config: ModuleConfig = { singleton: true }\n\n private receiver: WebhookReceiver\n private dispatcher?: WebhookDispatcher\n private echoConfig: EchoConfig\n\n constructor(config: EchoConfig = {}) {\n this.echoConfig = config\n this.receiver = new WebhookReceiver()\n\n // Register providers\n if (config.providers) {\n for (const [name, providerConfig] of Object.entries(config.providers)) {\n this.receiver.registerProvider(name, providerConfig.secret, {\n type: providerConfig.name,\n tolerance: providerConfig.tolerance,\n })\n }\n }\n\n // Create dispatcher\n if (config.dispatcher) {\n this.dispatcher = new WebhookDispatcher(config.dispatcher)\n }\n }\n\n /**\n * Install into PlanetCore\n */\n install(core: PlanetCore): void {\n // Bind instances\n core.container.bindSingleton('echo', this)\n core.container.bindSingleton('echo.receiver', this.receiver)\n if (this.dispatcher) {\n core.container.bindSingleton('echo.dispatcher', this.dispatcher)\n }\n }\n\n /**\n * Get webhook receiver\n */\n getReceiver(): WebhookReceiver {\n return this.receiver\n }\n\n /**\n * Get webhook dispatcher\n */\n getDispatcher(): WebhookDispatcher | undefined {\n return this.dispatcher\n }\n\n /**\n * Get configuration\n */\n getConfig(): EchoConfig {\n return this.echoConfig\n }\n}\n"
|
|
12
|
+
],
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "20EC60D051D827F464756E2164756E21",
|
|
15
|
+
"names": []
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gravito/echo",
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
|
+
"description": "Enterprise-grade webhook handling for Gravito. Secure receiving and reliable sending.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "bun run build.ts",
|
|
22
|
+
"test": "bun test",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"gravito",
|
|
27
|
+
"webhook",
|
|
28
|
+
"stripe",
|
|
29
|
+
"github",
|
|
30
|
+
"hmac",
|
|
31
|
+
"signature"
|
|
32
|
+
],
|
|
33
|
+
"author": "Carl Lee <carllee0520@gmail.com>",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/gravito-framework/gravito.git",
|
|
38
|
+
"directory": "packages/echo"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/gravito-framework/gravito#readme",
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"gravito-core": "1.0.0-beta.2"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"bun-types": "latest",
|
|
46
|
+
"typescript": "^5.9.3"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|