@hookflo/tern 4.3.0-beta.0 → 4.3.0-beta.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 +30 -17
- package/dist/adapters/cloudflare.d.ts +1 -2
- package/dist/adapters/cloudflare.js +5 -1
- package/dist/adapters/express.d.ts +1 -2
- package/dist/adapters/express.js +5 -1
- package/dist/adapters/hono.d.ts +1 -2
- package/dist/adapters/hono.js +5 -1
- package/dist/adapters/nextjs.d.ts +1 -2
- package/dist/adapters/nextjs.js +5 -1
- package/dist/adapters/shared.js +5 -2
- package/dist/index.d.ts +4 -5
- package/dist/index.js +23 -19
- package/dist/platforms/algorithms.d.ts +20 -0
- package/dist/platforms/algorithms.js +76 -1
- package/dist/types.d.ts +4 -65
- package/dist/types.js +3 -0
- package/dist/verifiers/algorithms.d.ts +6 -0
- package/dist/verifiers/algorithms.js +112 -18
- package/package.json +1 -1
- package/dist/normalization/index.d.ts +0 -20
- package/dist/normalization/index.js +0 -78
- package/dist/normalization/providers/payment/paypal.d.ts +0 -2
- package/dist/normalization/providers/payment/paypal.js +0 -12
- package/dist/normalization/providers/payment/razorpay.d.ts +0 -2
- package/dist/normalization/providers/payment/razorpay.js +0 -13
- package/dist/normalization/providers/payment/stripe.d.ts +0 -2
- package/dist/normalization/providers/payment/stripe.js +0 -13
- package/dist/normalization/providers/registry.d.ts +0 -5
- package/dist/normalization/providers/registry.js +0 -21
- package/dist/normalization/simple.d.ts +0 -4
- package/dist/normalization/simple.js +0 -126
- package/dist/normalization/storage/interface.d.ts +0 -13
- package/dist/normalization/storage/interface.js +0 -2
- package/dist/normalization/storage/memory.d.ts +0 -12
- package/dist/normalization/storage/memory.js +0 -39
- package/dist/normalization/templates/base/auth.d.ts +0 -2
- package/dist/normalization/templates/base/auth.js +0 -22
- package/dist/normalization/templates/base/ecommerce.d.ts +0 -2
- package/dist/normalization/templates/base/ecommerce.js +0 -25
- package/dist/normalization/templates/base/payment.d.ts +0 -2
- package/dist/normalization/templates/base/payment.js +0 -25
- package/dist/normalization/templates/registry.d.ts +0 -6
- package/dist/normalization/templates/registry.js +0 -22
- package/dist/normalization/transformer/engine.d.ts +0 -11
- package/dist/normalization/transformer/engine.js +0 -86
- package/dist/normalization/transformer/validator.d.ts +0 -12
- package/dist/normalization/transformer/validator.js +0 -56
- package/dist/normalization/types.d.ts +0 -79
- package/dist/normalization/types.js +0 -2
|
@@ -13,6 +13,37 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
13
13
|
this.config = config;
|
|
14
14
|
this.platform = platform;
|
|
15
15
|
}
|
|
16
|
+
getMissingSignatureMessage() {
|
|
17
|
+
return `Missing signature header: ${this.config.headerName}. Ensure your webhook provider sends this header and your adapter forwards it unchanged.`;
|
|
18
|
+
}
|
|
19
|
+
getMissingTimestampMessage() {
|
|
20
|
+
const timestampHeader = this.config.timestampHeader || this.config.customConfig?.timestampHeader || 'timestamp';
|
|
21
|
+
return `Missing required timestamp for webhook verification. Verify header '${timestampHeader}' is present and passed through by your framework/proxy.`;
|
|
22
|
+
}
|
|
23
|
+
getTimestampExpiredMessage() {
|
|
24
|
+
return 'Webhook timestamp expired. Check server clock drift and increase tolerance only if your provider allows it.';
|
|
25
|
+
}
|
|
26
|
+
getInvalidSignatureMessage() {
|
|
27
|
+
const genericHint = `Invalid signature for ${this.platform}. Confirm webhook secret, raw request body handling, and signature header formatting.`;
|
|
28
|
+
switch (this.platform) {
|
|
29
|
+
case 'stripe':
|
|
30
|
+
return `${genericHint} Stripe signatures require the exact raw body and Stripe-Signature timestamp/value pair.`;
|
|
31
|
+
case 'github':
|
|
32
|
+
return `${genericHint} GitHub signatures must include the sha256= prefix from x-hub-signature-256.`;
|
|
33
|
+
case 'svix':
|
|
34
|
+
case 'standardwebhooks':
|
|
35
|
+
case 'clerk':
|
|
36
|
+
case 'dodopayments':
|
|
37
|
+
case 'replicateai':
|
|
38
|
+
case 'polar':
|
|
39
|
+
return `${genericHint} Standard Webhooks payload must be signed as id.timestamp.body and secrets may need whsec_ base64 decoding.`;
|
|
40
|
+
default:
|
|
41
|
+
return genericHint;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
getVerificationErrorMessage(error) {
|
|
45
|
+
return `${this.platform} verification error: ${error.message}. Check webhook secret configuration and ensure your framework preserves raw body + headers.`;
|
|
46
|
+
}
|
|
16
47
|
parseDelimitedHeader(headerValue) {
|
|
17
48
|
const parts = headerValue.split(/[;,]/);
|
|
18
49
|
const values = {};
|
|
@@ -32,7 +63,9 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
32
63
|
return values;
|
|
33
64
|
}
|
|
34
65
|
extractSignatures(request) {
|
|
35
|
-
const headerValue = request.headers.get(this.config.headerName)
|
|
66
|
+
const headerValue = request.headers.get(this.config.headerName)
|
|
67
|
+
|| this.config.customConfig?.signatureHeaderAliases?.map((alias) => request.headers.get(alias)).find(Boolean)
|
|
68
|
+
|| null;
|
|
36
69
|
if (!headerValue)
|
|
37
70
|
return [];
|
|
38
71
|
switch (this.config.headerFormat) {
|
|
@@ -63,9 +96,34 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
63
96
|
}
|
|
64
97
|
// Accept "v1=<signature>" variants used by some providers/docs.
|
|
65
98
|
if (sig.startsWith("v1=")) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
99
|
+
if (this.config.customConfig?.comparePrefixed) {
|
|
100
|
+
for (const fragment of sig.split(',')) {
|
|
101
|
+
const candidate = fragment.trim();
|
|
102
|
+
if (candidate.startsWith('v1=')) {
|
|
103
|
+
normalized.push(candidate);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const [, value] = sig.split("=", 2);
|
|
109
|
+
if (value) {
|
|
110
|
+
normalized.push(value.trim());
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
for (const fragment of sig.split(',')) {
|
|
116
|
+
const candidate = fragment.trim();
|
|
117
|
+
if (candidate.startsWith('v1=')) {
|
|
118
|
+
if (this.config.customConfig?.comparePrefixed) {
|
|
119
|
+
normalized.push(candidate);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const [, value] = candidate.split('=', 2);
|
|
123
|
+
if (value) {
|
|
124
|
+
normalized.push(value.trim());
|
|
125
|
+
}
|
|
126
|
+
}
|
|
69
127
|
}
|
|
70
128
|
}
|
|
71
129
|
}
|
|
@@ -77,7 +135,9 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
77
135
|
extractTimestamp(request) {
|
|
78
136
|
if (!this.config.timestampHeader)
|
|
79
137
|
return null;
|
|
80
|
-
const timestampHeader = request.headers.get(this.config.timestampHeader)
|
|
138
|
+
const timestampHeader = request.headers.get(this.config.timestampHeader)
|
|
139
|
+
|| this.config.customConfig?.timestampHeaderAliases?.map((alias) => request.headers.get(alias)).find(Boolean)
|
|
140
|
+
|| null;
|
|
81
141
|
if (!timestampHeader)
|
|
82
142
|
return null;
|
|
83
143
|
switch (this.config.timestampFormat) {
|
|
@@ -152,10 +212,10 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
152
212
|
}
|
|
153
213
|
const customFormat = this.config.customConfig.payloadFormat;
|
|
154
214
|
if (customFormat.includes("{id}") && customFormat.includes("{timestamp}")) {
|
|
155
|
-
const id = request.headers.get(this.config.customConfig.idHeader || "x-webhook-id");
|
|
215
|
+
const id = request.headers.get(this.config.customConfig.idHeader || "x-webhook-id") || this.config.customConfig?.idHeaderAliases?.map((alias) => request.headers.get(alias)).find(Boolean);
|
|
156
216
|
const timestamp = request.headers.get(this.config.timestampHeader ||
|
|
157
217
|
this.config.customConfig?.timestampHeader ||
|
|
158
|
-
"x-webhook-timestamp");
|
|
218
|
+
"x-webhook-timestamp") || this.config.customConfig?.timestampHeaderAliases?.map((alias) => request.headers.get(alias)).find(Boolean);
|
|
159
219
|
// if either is missing payload will be malformed — fail explicitly
|
|
160
220
|
if (!id || !timestamp) {
|
|
161
221
|
throw new Error(`Missing required headers for payload construction: ${!id ? this.config.customConfig.idHeader || "x-webhook-id" : ""} ${!timestamp
|
|
@@ -167,6 +227,11 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
167
227
|
.replace("{timestamp}", timestamp.trim() || "")
|
|
168
228
|
.replace("{body}", rawBody);
|
|
169
229
|
}
|
|
230
|
+
if (customFormat.includes('{url}')) {
|
|
231
|
+
return customFormat
|
|
232
|
+
.replace('{url}', request.url)
|
|
233
|
+
.replace('{body}', rawBody);
|
|
234
|
+
}
|
|
170
235
|
if (customFormat.includes("{timestamp}") &&
|
|
171
236
|
customFormat.includes("{body}")) {
|
|
172
237
|
const timestamp = this.extractTimestampFromSignature(request) ||
|
|
@@ -250,6 +315,26 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
250
315
|
}
|
|
251
316
|
exports.AlgorithmBasedVerifier = AlgorithmBasedVerifier;
|
|
252
317
|
class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
318
|
+
validateLinearReplayWindow(rawBody) {
|
|
319
|
+
if (this.platform !== 'linear')
|
|
320
|
+
return null;
|
|
321
|
+
try {
|
|
322
|
+
const parsed = JSON.parse(rawBody);
|
|
323
|
+
const rawTimestamp = parsed.webhookTimestamp;
|
|
324
|
+
const timestampMs = Number(rawTimestamp);
|
|
325
|
+
if (!Number.isFinite(timestampMs)) {
|
|
326
|
+
return 'Missing or invalid Linear webhookTimestamp';
|
|
327
|
+
}
|
|
328
|
+
const replayToleranceMs = this.config.customConfig?.replayToleranceMs || 60000;
|
|
329
|
+
if (Math.abs(Date.now() - timestampMs) > replayToleranceMs) {
|
|
330
|
+
return 'Linear webhook timestamp is outside the replay window';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return 'Linear webhook replay check requires JSON payload';
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
253
338
|
resolveSentryPayloadCandidates(rawBody, request) {
|
|
254
339
|
const candidates = [
|
|
255
340
|
this.formatPayload(rawBody, request),
|
|
@@ -283,12 +368,21 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
283
368
|
if (signatures.length === 0) {
|
|
284
369
|
return {
|
|
285
370
|
isValid: false,
|
|
286
|
-
error:
|
|
371
|
+
error: this.getMissingSignatureMessage(),
|
|
287
372
|
errorCode: "MISSING_SIGNATURE",
|
|
288
373
|
platform: this.platform,
|
|
289
374
|
};
|
|
290
375
|
}
|
|
291
376
|
const rawBody = await request.text();
|
|
377
|
+
const linearReplayError = this.validateLinearReplayWindow(rawBody);
|
|
378
|
+
if (linearReplayError) {
|
|
379
|
+
return {
|
|
380
|
+
isValid: false,
|
|
381
|
+
error: linearReplayError,
|
|
382
|
+
errorCode: 'TIMESTAMP_EXPIRED',
|
|
383
|
+
platform: this.platform,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
292
386
|
let timestamp = null;
|
|
293
387
|
if (this.config.headerFormat === "comma-separated") {
|
|
294
388
|
timestamp = this.extractTimestampFromSignature(request);
|
|
@@ -299,7 +393,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
299
393
|
if (this.requiresTimestamp() && !timestamp) {
|
|
300
394
|
return {
|
|
301
395
|
isValid: false,
|
|
302
|
-
error:
|
|
396
|
+
error: this.getMissingTimestampMessage(),
|
|
303
397
|
errorCode: 'MISSING_SIGNATURE',
|
|
304
398
|
platform: this.platform,
|
|
305
399
|
};
|
|
@@ -307,7 +401,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
307
401
|
if (timestamp && !this.isTimestampValid(timestamp)) {
|
|
308
402
|
return {
|
|
309
403
|
isValid: false,
|
|
310
|
-
error:
|
|
404
|
+
error: this.getTimestampExpiredMessage(),
|
|
311
405
|
errorCode: "TIMESTAMP_EXPIRED",
|
|
312
406
|
platform: this.platform,
|
|
313
407
|
};
|
|
@@ -322,7 +416,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
322
416
|
if (this.config.customConfig?.encoding === "base64") {
|
|
323
417
|
isValid = this.verifyHMACWithBase64(payload, signature, algorithm);
|
|
324
418
|
}
|
|
325
|
-
else if (this.config.headerFormat === "prefixed") {
|
|
419
|
+
else if (this.config.headerFormat === "prefixed" || this.config.customConfig?.comparePrefixed) {
|
|
326
420
|
isValid = this.verifyHMACWithPrefix(payload, signature, algorithm);
|
|
327
421
|
}
|
|
328
422
|
else {
|
|
@@ -339,7 +433,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
339
433
|
if (!isValid) {
|
|
340
434
|
return {
|
|
341
435
|
isValid: false,
|
|
342
|
-
error:
|
|
436
|
+
error: this.getInvalidSignatureMessage(),
|
|
343
437
|
errorCode: "INVALID_SIGNATURE",
|
|
344
438
|
platform: this.platform,
|
|
345
439
|
};
|
|
@@ -368,7 +462,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
368
462
|
catch (error) {
|
|
369
463
|
return {
|
|
370
464
|
isValid: false,
|
|
371
|
-
error:
|
|
465
|
+
error: this.getVerificationErrorMessage(error),
|
|
372
466
|
errorCode: "VERIFICATION_ERROR",
|
|
373
467
|
platform: this.platform,
|
|
374
468
|
};
|
|
@@ -477,7 +571,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
|
|
|
477
571
|
if (signatures.length === 0) {
|
|
478
572
|
return {
|
|
479
573
|
isValid: false,
|
|
480
|
-
error:
|
|
574
|
+
error: this.getMissingSignatureMessage(),
|
|
481
575
|
errorCode: "MISSING_SIGNATURE",
|
|
482
576
|
platform: this.platform,
|
|
483
577
|
};
|
|
@@ -491,7 +585,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
|
|
|
491
585
|
if (!timestampStr) {
|
|
492
586
|
return {
|
|
493
587
|
isValid: false,
|
|
494
|
-
error:
|
|
588
|
+
error: this.getMissingTimestampMessage(),
|
|
495
589
|
errorCode: 'MISSING_SIGNATURE',
|
|
496
590
|
platform: this.platform,
|
|
497
591
|
};
|
|
@@ -500,7 +594,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
|
|
|
500
594
|
if (!this.isTimestampValid(timestamp)) {
|
|
501
595
|
return {
|
|
502
596
|
isValid: false,
|
|
503
|
-
error:
|
|
597
|
+
error: this.getTimestampExpiredMessage(),
|
|
504
598
|
errorCode: "TIMESTAMP_EXPIRED",
|
|
505
599
|
platform: this.platform,
|
|
506
600
|
};
|
|
@@ -560,7 +654,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
|
|
|
560
654
|
if (!isValid) {
|
|
561
655
|
return {
|
|
562
656
|
isValid: false,
|
|
563
|
-
error:
|
|
657
|
+
error: this.getInvalidSignatureMessage(),
|
|
564
658
|
errorCode: "INVALID_SIGNATURE",
|
|
565
659
|
platform: this.platform,
|
|
566
660
|
};
|
|
@@ -589,7 +683,7 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
|
|
|
589
683
|
catch (error) {
|
|
590
684
|
return {
|
|
591
685
|
isValid: false,
|
|
592
|
-
error:
|
|
686
|
+
error: this.getVerificationErrorMessage(error),
|
|
593
687
|
errorCode: "VERIFICATION_ERROR",
|
|
594
688
|
platform: this.platform,
|
|
595
689
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hookflo/tern",
|
|
3
|
-
"version": "4.3.0-beta.
|
|
3
|
+
"version": "4.3.0-beta.2",
|
|
4
4
|
"description": "A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { BaseTemplate, CreateSchemaInput, NormalizedResult, ProviderInfo, TemplateCategory, TransformParams, UpdateSchemaInput, UserSchema } from './types';
|
|
2
|
-
import { StorageAdapter } from './storage/interface';
|
|
3
|
-
export declare class Normalizer {
|
|
4
|
-
private readonly storage;
|
|
5
|
-
private engine;
|
|
6
|
-
constructor(storage?: StorageAdapter);
|
|
7
|
-
getBaseTemplates(): Promise<BaseTemplate[]>;
|
|
8
|
-
getProviders(category?: TemplateCategory): Promise<ProviderInfo[]>;
|
|
9
|
-
createSchema(input: CreateSchemaInput): Promise<UserSchema>;
|
|
10
|
-
updateSchema(schemaId: string, updates: UpdateSchemaInput): Promise<void>;
|
|
11
|
-
getSchema(id: string): Promise<UserSchema | null>;
|
|
12
|
-
transform(params: TransformParams): Promise<NormalizedResult>;
|
|
13
|
-
validateSchema(schema: UserSchema): Promise<{
|
|
14
|
-
valid: boolean;
|
|
15
|
-
errors: string[];
|
|
16
|
-
}>;
|
|
17
|
-
}
|
|
18
|
-
export * from './types';
|
|
19
|
-
export * from './storage/interface';
|
|
20
|
-
export { InMemoryStorageAdapter } from './storage/memory';
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.InMemoryStorageAdapter = exports.Normalizer = void 0;
|
|
18
|
-
const registry_1 = require("./providers/registry");
|
|
19
|
-
const registry_2 = require("./templates/registry");
|
|
20
|
-
const memory_1 = require("./storage/memory");
|
|
21
|
-
const engine_1 = require("./transformer/engine");
|
|
22
|
-
const validator_1 = require("./transformer/validator");
|
|
23
|
-
class Normalizer {
|
|
24
|
-
constructor(storage = new memory_1.InMemoryStorageAdapter()) {
|
|
25
|
-
this.storage = storage;
|
|
26
|
-
this.engine = new engine_1.NormalizationEngine(storage, new validator_1.SchemaValidator());
|
|
27
|
-
}
|
|
28
|
-
async getBaseTemplates() {
|
|
29
|
-
return this.storage.listBaseTemplates();
|
|
30
|
-
}
|
|
31
|
-
async getProviders(category) {
|
|
32
|
-
return registry_1.providerRegistry.list(category);
|
|
33
|
-
}
|
|
34
|
-
async createSchema(input) {
|
|
35
|
-
const schema = {
|
|
36
|
-
id: generateId(),
|
|
37
|
-
userId: input.userId,
|
|
38
|
-
baseTemplateId: input.baseTemplateId,
|
|
39
|
-
category: input.category,
|
|
40
|
-
fields: input.fields,
|
|
41
|
-
providerMappings: input.providerMappings,
|
|
42
|
-
createdAt: new Date(),
|
|
43
|
-
updatedAt: new Date(),
|
|
44
|
-
};
|
|
45
|
-
await this.storage.saveSchema(schema);
|
|
46
|
-
return schema;
|
|
47
|
-
}
|
|
48
|
-
async updateSchema(schemaId, updates) {
|
|
49
|
-
await this.storage.updateSchema(schemaId, updates);
|
|
50
|
-
}
|
|
51
|
-
async getSchema(id) {
|
|
52
|
-
return this.storage.getSchema(id);
|
|
53
|
-
}
|
|
54
|
-
async transform(params) {
|
|
55
|
-
return this.engine.transform(params);
|
|
56
|
-
}
|
|
57
|
-
async validateSchema(schema) {
|
|
58
|
-
const base = (await this.storage.getBaseTemplate(schema.baseTemplateId))
|
|
59
|
-
?? registry_2.templateRegistry.getById(schema.baseTemplateId);
|
|
60
|
-
if (!base) {
|
|
61
|
-
return {
|
|
62
|
-
valid: false,
|
|
63
|
-
errors: [`Base template not found: ${schema.baseTemplateId}`],
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
const validator = new validator_1.SchemaValidator();
|
|
67
|
-
return validator.validateSchema(schema, base);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
exports.Normalizer = Normalizer;
|
|
71
|
-
function generateId() {
|
|
72
|
-
// Simple non-crypto unique ID generator for framework default
|
|
73
|
-
return (`sch_${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`);
|
|
74
|
-
}
|
|
75
|
-
__exportStar(require("./types"), exports);
|
|
76
|
-
__exportStar(require("./storage/interface"), exports);
|
|
77
|
-
var memory_2 = require("./storage/memory");
|
|
78
|
-
Object.defineProperty(exports, "InMemoryStorageAdapter", { enumerable: true, get: function () { return memory_2.InMemoryStorageAdapter; } });
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.paypalDefaultMapping = void 0;
|
|
4
|
-
exports.paypalDefaultMapping = {
|
|
5
|
-
provider: 'paypal',
|
|
6
|
-
fieldMappings: [
|
|
7
|
-
{ schemaFieldId: 'event_type', providerPath: 'event_type' },
|
|
8
|
-
{ schemaFieldId: 'amount', providerPath: 'resource.amount.value', transform: 'toNumber' },
|
|
9
|
-
{ schemaFieldId: 'currency', providerPath: 'resource.amount.currency_code', transform: 'toUpperCase' },
|
|
10
|
-
{ schemaFieldId: 'transaction_id', providerPath: 'resource.id' },
|
|
11
|
-
],
|
|
12
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.razorpayDefaultMapping = void 0;
|
|
4
|
-
exports.razorpayDefaultMapping = {
|
|
5
|
-
provider: 'razorpay',
|
|
6
|
-
fieldMappings: [
|
|
7
|
-
{ schemaFieldId: 'event_type', providerPath: 'event' },
|
|
8
|
-
{ schemaFieldId: 'amount', providerPath: 'payload.payment.entity.amount' },
|
|
9
|
-
{ schemaFieldId: 'currency', providerPath: 'payload.payment.entity.currency', transform: 'toUpperCase' },
|
|
10
|
-
{ schemaFieldId: 'transaction_id', providerPath: 'payload.payment.entity.id' },
|
|
11
|
-
{ schemaFieldId: 'customer_id', providerPath: 'payload.payment.entity.contact' },
|
|
12
|
-
],
|
|
13
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.stripeDefaultMapping = void 0;
|
|
4
|
-
exports.stripeDefaultMapping = {
|
|
5
|
-
provider: 'stripe',
|
|
6
|
-
fieldMappings: [
|
|
7
|
-
{ schemaFieldId: 'event_type', providerPath: 'type' },
|
|
8
|
-
{ schemaFieldId: 'amount', providerPath: 'data.object.amount_received' },
|
|
9
|
-
{ schemaFieldId: 'currency', providerPath: 'data.object.currency', transform: 'toUpperCase' },
|
|
10
|
-
{ schemaFieldId: 'transaction_id', providerPath: 'data.object.id' },
|
|
11
|
-
{ schemaFieldId: 'customer_id', providerPath: 'data.object.customer' },
|
|
12
|
-
],
|
|
13
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.providerRegistry = void 0;
|
|
4
|
-
const providers = [
|
|
5
|
-
{ id: 'stripe', name: 'Stripe', category: 'payment' },
|
|
6
|
-
{ id: 'razorpay', name: 'Razorpay', category: 'payment' },
|
|
7
|
-
{ id: 'paypal', name: 'PayPal', category: 'payment' },
|
|
8
|
-
{ id: 'clerk', name: 'Clerk', category: 'auth' },
|
|
9
|
-
{ id: 'shopify', name: 'Shopify', category: 'ecommerce' },
|
|
10
|
-
{ id: 'woocommerce', name: 'WooCommerce', category: 'ecommerce' },
|
|
11
|
-
];
|
|
12
|
-
exports.providerRegistry = {
|
|
13
|
-
list(category) {
|
|
14
|
-
if (!category)
|
|
15
|
-
return providers;
|
|
16
|
-
return providers.filter((p) => p.category === category);
|
|
17
|
-
},
|
|
18
|
-
getById(id) {
|
|
19
|
-
return providers.find((p) => p.id === id);
|
|
20
|
-
},
|
|
21
|
-
};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { AnyNormalizedWebhook, NormalizeOptions, NormalizationCategory, WebhookPlatform } from '../types';
|
|
2
|
-
export declare function getPlatformNormalizationCategory(platform: WebhookPlatform): NormalizationCategory | null;
|
|
3
|
-
export declare function getPlatformsByCategory(category: NormalizationCategory): WebhookPlatform[];
|
|
4
|
-
export declare function normalizePayload(platform: WebhookPlatform, payload: any, normalize?: boolean | NormalizeOptions): AnyNormalizedWebhook | unknown;
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getPlatformNormalizationCategory = getPlatformNormalizationCategory;
|
|
4
|
-
exports.getPlatformsByCategory = getPlatformsByCategory;
|
|
5
|
-
exports.normalizePayload = normalizePayload;
|
|
6
|
-
function readPath(payload, path) {
|
|
7
|
-
return path.split('.').reduce((acc, key) => {
|
|
8
|
-
if (acc === undefined || acc === null) {
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
11
|
-
return acc[key];
|
|
12
|
-
}, payload);
|
|
13
|
-
}
|
|
14
|
-
const platformNormalizers = {
|
|
15
|
-
stripe: {
|
|
16
|
-
platform: 'stripe',
|
|
17
|
-
category: 'payment',
|
|
18
|
-
normalize: (payload) => ({
|
|
19
|
-
category: 'payment',
|
|
20
|
-
event: readPath(payload, 'type') === 'payment_intent.succeeded'
|
|
21
|
-
? 'payment.succeeded'
|
|
22
|
-
: 'payment.unknown',
|
|
23
|
-
amount: readPath(payload, 'data.object.amount_received')
|
|
24
|
-
?? readPath(payload, 'data.object.amount'),
|
|
25
|
-
currency: String(readPath(payload, 'data.object.currency') ?? '').toUpperCase() || undefined,
|
|
26
|
-
customer_id: readPath(payload, 'data.object.customer'),
|
|
27
|
-
transaction_id: readPath(payload, 'data.object.id'),
|
|
28
|
-
metadata: {},
|
|
29
|
-
occurred_at: new Date().toISOString(),
|
|
30
|
-
}),
|
|
31
|
-
},
|
|
32
|
-
polar: {
|
|
33
|
-
platform: 'polar',
|
|
34
|
-
category: 'payment',
|
|
35
|
-
normalize: (payload) => ({
|
|
36
|
-
category: 'payment',
|
|
37
|
-
event: readPath(payload, 'event') === 'payment.completed'
|
|
38
|
-
? 'payment.succeeded'
|
|
39
|
-
: 'payment.unknown',
|
|
40
|
-
amount: readPath(payload, 'payload.amount_cents'),
|
|
41
|
-
currency: String(readPath(payload, 'payload.currency_code') ?? '').toUpperCase() || undefined,
|
|
42
|
-
customer_id: readPath(payload, 'payload.customer_id'),
|
|
43
|
-
transaction_id: readPath(payload, 'payload.transaction_id'),
|
|
44
|
-
metadata: {},
|
|
45
|
-
occurred_at: new Date().toISOString(),
|
|
46
|
-
}),
|
|
47
|
-
},
|
|
48
|
-
clerk: {
|
|
49
|
-
platform: 'clerk',
|
|
50
|
-
category: 'auth',
|
|
51
|
-
normalize: (payload) => ({
|
|
52
|
-
category: 'auth',
|
|
53
|
-
event: readPath(payload, 'type') || 'auth.unknown',
|
|
54
|
-
user_id: readPath(payload, 'data.id'),
|
|
55
|
-
email: readPath(payload, 'data.email_addresses.0.email_address'),
|
|
56
|
-
metadata: {},
|
|
57
|
-
occurred_at: new Date().toISOString(),
|
|
58
|
-
}),
|
|
59
|
-
},
|
|
60
|
-
vercel: {
|
|
61
|
-
platform: 'vercel',
|
|
62
|
-
category: 'infrastructure',
|
|
63
|
-
normalize: (payload) => ({
|
|
64
|
-
category: 'infrastructure',
|
|
65
|
-
event: readPath(payload, 'type') || 'deployment.unknown',
|
|
66
|
-
project_id: readPath(payload, 'payload.project.id'),
|
|
67
|
-
deployment_id: readPath(payload, 'payload.deployment.id'),
|
|
68
|
-
status: 'unknown',
|
|
69
|
-
metadata: {},
|
|
70
|
-
occurred_at: new Date().toISOString(),
|
|
71
|
-
}),
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
function getPlatformNormalizationCategory(platform) {
|
|
75
|
-
return platformNormalizers[platform]?.category || null;
|
|
76
|
-
}
|
|
77
|
-
function getPlatformsByCategory(category) {
|
|
78
|
-
return Object.values(platformNormalizers)
|
|
79
|
-
.filter((spec) => !!spec)
|
|
80
|
-
.filter((spec) => spec.category === category)
|
|
81
|
-
.map((spec) => spec.platform);
|
|
82
|
-
}
|
|
83
|
-
function resolveNormalizeOptions(normalize) {
|
|
84
|
-
if (typeof normalize === 'boolean') {
|
|
85
|
-
return {
|
|
86
|
-
enabled: normalize,
|
|
87
|
-
category: undefined,
|
|
88
|
-
includeRaw: true,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
enabled: normalize?.enabled ?? true,
|
|
93
|
-
category: normalize?.category,
|
|
94
|
-
includeRaw: normalize?.includeRaw ?? true,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function buildUnknownNormalizedPayload(platform, payload, category, includeRaw, warning) {
|
|
98
|
-
return {
|
|
99
|
-
category: category || 'infrastructure',
|
|
100
|
-
event: payload?.type ?? payload?.event ?? 'unknown',
|
|
101
|
-
_platform: platform,
|
|
102
|
-
_raw: includeRaw ? payload : undefined,
|
|
103
|
-
warning,
|
|
104
|
-
occurred_at: new Date().toISOString(),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function normalizePayload(platform, payload, normalize) {
|
|
108
|
-
const options = resolveNormalizeOptions(normalize);
|
|
109
|
-
if (!options.enabled) {
|
|
110
|
-
return payload;
|
|
111
|
-
}
|
|
112
|
-
const spec = platformNormalizers[platform];
|
|
113
|
-
const inferredCategory = spec?.category;
|
|
114
|
-
if (!spec) {
|
|
115
|
-
return buildUnknownNormalizedPayload(platform, payload, options.category, options.includeRaw);
|
|
116
|
-
}
|
|
117
|
-
if (options.category && options.category !== inferredCategory) {
|
|
118
|
-
return buildUnknownNormalizedPayload(platform, payload, inferredCategory, options.includeRaw, `Requested normalization category '${options.category}' does not match platform category '${inferredCategory}'`);
|
|
119
|
-
}
|
|
120
|
-
const normalized = spec.normalize(payload);
|
|
121
|
-
return {
|
|
122
|
-
...normalized,
|
|
123
|
-
_platform: platform,
|
|
124
|
-
_raw: options.includeRaw ? payload : undefined,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { BaseTemplate, UpdateSchemaInput, UserSchema } from '../types';
|
|
2
|
-
export interface StorageAdapter {
|
|
3
|
-
saveSchema(schema: UserSchema): Promise<void>;
|
|
4
|
-
getSchema(id: string): Promise<UserSchema | null>;
|
|
5
|
-
updateSchema(id: string, updates: UpdateSchemaInput): Promise<void>;
|
|
6
|
-
deleteSchema(id: string): Promise<void>;
|
|
7
|
-
listSchemas(userId: string): Promise<UserSchema[]>;
|
|
8
|
-
getBaseTemplate(id: string): Promise<BaseTemplate | null>;
|
|
9
|
-
listBaseTemplates(): Promise<BaseTemplate[]>;
|
|
10
|
-
}
|
|
11
|
-
export interface NormalizationStorageOptions {
|
|
12
|
-
adapter: StorageAdapter;
|
|
13
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { BaseTemplate, UpdateSchemaInput, UserSchema } from '../types';
|
|
2
|
-
import { StorageAdapter } from './interface';
|
|
3
|
-
export declare class InMemoryStorageAdapter implements StorageAdapter {
|
|
4
|
-
private schemas;
|
|
5
|
-
saveSchema(schema: UserSchema): Promise<void>;
|
|
6
|
-
getSchema(id: string): Promise<UserSchema | null>;
|
|
7
|
-
updateSchema(id: string, updates: UpdateSchemaInput): Promise<void>;
|
|
8
|
-
deleteSchema(id: string): Promise<void>;
|
|
9
|
-
listSchemas(userId: string): Promise<UserSchema[]>;
|
|
10
|
-
getBaseTemplate(id: string): Promise<BaseTemplate | null>;
|
|
11
|
-
listBaseTemplates(): Promise<BaseTemplate[]>;
|
|
12
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InMemoryStorageAdapter = void 0;
|
|
4
|
-
const registry_1 = require("../templates/registry");
|
|
5
|
-
class InMemoryStorageAdapter {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.schemas = new Map();
|
|
8
|
-
}
|
|
9
|
-
async saveSchema(schema) {
|
|
10
|
-
this.schemas.set(schema.id, schema);
|
|
11
|
-
}
|
|
12
|
-
async getSchema(id) {
|
|
13
|
-
return this.schemas.get(id) ?? null;
|
|
14
|
-
}
|
|
15
|
-
async updateSchema(id, updates) {
|
|
16
|
-
const existing = this.schemas.get(id);
|
|
17
|
-
if (!existing)
|
|
18
|
-
return;
|
|
19
|
-
const updated = {
|
|
20
|
-
...existing,
|
|
21
|
-
...updates,
|
|
22
|
-
updatedAt: new Date(),
|
|
23
|
-
};
|
|
24
|
-
this.schemas.set(id, updated);
|
|
25
|
-
}
|
|
26
|
-
async deleteSchema(id) {
|
|
27
|
-
this.schemas.delete(id);
|
|
28
|
-
}
|
|
29
|
-
async listSchemas(userId) {
|
|
30
|
-
return Array.from(this.schemas.values()).filter((s) => s.userId === userId);
|
|
31
|
-
}
|
|
32
|
-
async getBaseTemplate(id) {
|
|
33
|
-
return registry_1.templateRegistry.getById(id) ?? null;
|
|
34
|
-
}
|
|
35
|
-
async listBaseTemplates() {
|
|
36
|
-
return registry_1.templateRegistry.listAll();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
exports.InMemoryStorageAdapter = InMemoryStorageAdapter;
|