@hookflo/tern 4.1.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -438
- package/dist/adapters/cloudflare.d.ts +3 -0
- package/dist/adapters/cloudflare.js +25 -1
- package/dist/adapters/express.d.ts +3 -0
- package/dist/adapters/express.js +16 -0
- package/dist/adapters/nextjs.d.ts +3 -0
- package/dist/adapters/nextjs.js +25 -1
- package/dist/alerts.d.ts +7 -0
- package/dist/alerts.js +21 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -1
- package/dist/notifications/channels/discord/build-payload.d.ts +17 -0
- package/dist/notifications/channels/discord/build-payload.js +53 -0
- package/dist/notifications/channels/discord/index.d.ts +1 -0
- package/dist/notifications/channels/discord/index.js +5 -0
- package/dist/notifications/channels/slack/build-payload.d.ts +5 -0
- package/dist/notifications/channels/slack/build-payload.js +82 -0
- package/dist/notifications/channels/slack/index.d.ts +1 -0
- package/dist/notifications/channels/slack/index.js +5 -0
- package/dist/notifications/constants.d.ts +8 -0
- package/dist/notifications/constants.js +15 -0
- package/dist/notifications/dispatch.d.ts +8 -0
- package/dist/notifications/dispatch.js +13 -0
- package/dist/notifications/index.d.ts +10 -0
- package/dist/notifications/index.js +27 -0
- package/dist/notifications/send-alert.d.ts +2 -0
- package/dist/notifications/send-alert.js +69 -0
- package/dist/notifications/types.d.ts +48 -0
- package/dist/notifications/types.js +2 -0
- package/dist/notifications/utils.d.ts +4 -0
- package/dist/notifications/utils.js +80 -0
- package/dist/upstash/controls.d.ts +2 -6
- package/dist/upstash/controls.js +63 -0
- package/dist/upstash/index.d.ts +1 -1
- package/dist/upstash/queue.js +5 -1
- package/dist/upstash/types.d.ts +18 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WebhookPlatform, NormalizeOptions } from '../types';
|
|
2
2
|
import { QueueOption } from '../upstash/types';
|
|
3
|
+
import type { AlertConfig, SendAlertOptions } from '../notifications/types';
|
|
3
4
|
export interface CloudflareWebhookHandlerOptions<TEnv = Record<string, unknown>, TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
|
|
4
5
|
platform: WebhookPlatform;
|
|
5
6
|
secret?: string;
|
|
@@ -7,6 +8,8 @@ export interface CloudflareWebhookHandlerOptions<TEnv = Record<string, unknown>,
|
|
|
7
8
|
toleranceInSeconds?: number;
|
|
8
9
|
normalize?: boolean | NormalizeOptions;
|
|
9
10
|
queue?: QueueOption;
|
|
11
|
+
alerts?: AlertConfig;
|
|
12
|
+
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
|
10
13
|
onError?: (error: Error) => void;
|
|
11
14
|
handler: (payload: TPayload, env: TEnv, metadata: TMetadata) => Promise<TResponse> | TResponse;
|
|
12
15
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createWebhookHandler = createWebhookHandler;
|
|
4
4
|
const index_1 = require("../index");
|
|
5
5
|
const queue_1 = require("../upstash/queue");
|
|
6
|
+
const dispatch_1 = require("../notifications/dispatch");
|
|
6
7
|
function createWebhookHandler(options) {
|
|
7
8
|
return async (request, env) => {
|
|
8
9
|
try {
|
|
@@ -13,18 +14,41 @@ function createWebhookHandler(options) {
|
|
|
13
14
|
}
|
|
14
15
|
if (options.queue) {
|
|
15
16
|
const queueConfig = (0, queue_1.resolveQueueConfig)(options.queue);
|
|
16
|
-
|
|
17
|
+
const response = await (0, queue_1.handleQueuedRequest)(request, {
|
|
17
18
|
platform: options.platform,
|
|
18
19
|
secret,
|
|
19
20
|
queueConfig,
|
|
20
21
|
handler: (payload, metadata) => options.handler(payload, env, metadata),
|
|
21
22
|
toleranceInSeconds: options.toleranceInSeconds ?? 300,
|
|
22
23
|
});
|
|
24
|
+
if (response.ok) {
|
|
25
|
+
let eventId;
|
|
26
|
+
try {
|
|
27
|
+
const body = await response.clone().json();
|
|
28
|
+
eventId = typeof body.eventId === 'string' ? body.eventId : undefined;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
eventId = undefined;
|
|
32
|
+
}
|
|
33
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
34
|
+
alerts: options.alerts,
|
|
35
|
+
source: options.platform,
|
|
36
|
+
eventId,
|
|
37
|
+
alert: options.alert,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
23
41
|
}
|
|
24
42
|
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, options.platform, secret, options.toleranceInSeconds, options.normalize);
|
|
25
43
|
if (!result.isValid) {
|
|
26
44
|
return Response.json({ error: result.error, errorCode: result.errorCode, platform: result.platform, metadata: result.metadata }, { status: 400 });
|
|
27
45
|
}
|
|
46
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
47
|
+
alerts: options.alerts,
|
|
48
|
+
source: options.platform,
|
|
49
|
+
eventId: result.eventId,
|
|
50
|
+
alert: options.alert,
|
|
51
|
+
});
|
|
28
52
|
const data = await options.handler(result.payload, env, (result.metadata || {}));
|
|
29
53
|
return Response.json(data);
|
|
30
54
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WebhookPlatform, WebhookVerificationResult, NormalizeOptions } from '../types';
|
|
2
2
|
import { QueueOption } from '../upstash/types';
|
|
3
3
|
import { MinimalNodeRequest } from './shared';
|
|
4
|
+
import type { AlertConfig, SendAlertOptions } from '../notifications/types';
|
|
4
5
|
export interface ExpressLikeResponse {
|
|
5
6
|
status: (code: number) => ExpressLikeResponse;
|
|
6
7
|
json: (payload: unknown) => unknown;
|
|
@@ -15,6 +16,8 @@ export interface ExpressWebhookMiddlewareOptions {
|
|
|
15
16
|
toleranceInSeconds?: number;
|
|
16
17
|
normalize?: boolean | NormalizeOptions;
|
|
17
18
|
queue?: QueueOption;
|
|
19
|
+
alerts?: AlertConfig;
|
|
20
|
+
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
|
18
21
|
onError?: (error: Error) => void;
|
|
19
22
|
strictRawBody?: boolean;
|
|
20
23
|
}
|
package/dist/adapters/express.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.createWebhookMiddleware = createWebhookMiddleware;
|
|
|
4
4
|
const index_1 = require("../index");
|
|
5
5
|
const queue_1 = require("../upstash/queue");
|
|
6
6
|
const shared_1 = require("./shared");
|
|
7
|
+
const dispatch_1 = require("../notifications/dispatch");
|
|
7
8
|
function createWebhookMiddleware(options) {
|
|
8
9
|
return async (req, res, next) => {
|
|
9
10
|
try {
|
|
@@ -36,6 +37,15 @@ function createWebhookMiddleware(options) {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
res.status(queueResponse.status).json(body ?? {});
|
|
40
|
+
if (queueResponse.ok) {
|
|
41
|
+
const queueResult = body && typeof body === 'object' ? body : undefined;
|
|
42
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
43
|
+
alerts: options.alerts,
|
|
44
|
+
source: options.platform,
|
|
45
|
+
eventId: typeof queueResult?.eventId === 'string' ? queueResult.eventId : undefined,
|
|
46
|
+
alert: options.alert,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
39
49
|
return;
|
|
40
50
|
}
|
|
41
51
|
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(webRequest, options.platform, options.secret, options.toleranceInSeconds, options.normalize);
|
|
@@ -49,6 +59,12 @@ function createWebhookMiddleware(options) {
|
|
|
49
59
|
return;
|
|
50
60
|
}
|
|
51
61
|
req.webhook = result;
|
|
62
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
63
|
+
alerts: options.alerts,
|
|
64
|
+
source: options.platform,
|
|
65
|
+
eventId: result.eventId,
|
|
66
|
+
alert: options.alert,
|
|
67
|
+
});
|
|
52
68
|
next();
|
|
53
69
|
}
|
|
54
70
|
catch (error) {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { WebhookPlatform, NormalizeOptions } from '../types';
|
|
2
2
|
import { QueueOption } from '../upstash/types';
|
|
3
|
+
import type { AlertConfig, SendAlertOptions } from '../notifications/types';
|
|
3
4
|
export interface NextWebhookHandlerOptions<TPayload = any, TMetadata extends Record<string, unknown> = Record<string, unknown>, TResponse = unknown> {
|
|
4
5
|
platform: WebhookPlatform;
|
|
5
6
|
secret: string;
|
|
6
7
|
toleranceInSeconds?: number;
|
|
7
8
|
normalize?: boolean | NormalizeOptions;
|
|
8
9
|
queue?: QueueOption;
|
|
10
|
+
alerts?: AlertConfig;
|
|
11
|
+
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
|
9
12
|
onError?: (error: Error) => void;
|
|
10
13
|
handler: (payload: TPayload, metadata: TMetadata) => Promise<TResponse> | TResponse;
|
|
11
14
|
}
|
package/dist/adapters/nextjs.js
CHANGED
|
@@ -3,23 +3,47 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createWebhookHandler = createWebhookHandler;
|
|
4
4
|
const index_1 = require("../index");
|
|
5
5
|
const queue_1 = require("../upstash/queue");
|
|
6
|
+
const dispatch_1 = require("../notifications/dispatch");
|
|
6
7
|
function createWebhookHandler(options) {
|
|
7
8
|
return async (request) => {
|
|
8
9
|
try {
|
|
9
10
|
if (options.queue) {
|
|
10
11
|
const queueConfig = (0, queue_1.resolveQueueConfig)(options.queue);
|
|
11
|
-
|
|
12
|
+
const response = await (0, queue_1.handleQueuedRequest)(request, {
|
|
12
13
|
platform: options.platform,
|
|
13
14
|
secret: options.secret,
|
|
14
15
|
queueConfig,
|
|
15
16
|
handler: options.handler,
|
|
16
17
|
toleranceInSeconds: options.toleranceInSeconds ?? 300,
|
|
17
18
|
});
|
|
19
|
+
if (response.ok) {
|
|
20
|
+
let eventId;
|
|
21
|
+
try {
|
|
22
|
+
const body = await response.clone().json();
|
|
23
|
+
eventId = typeof body.eventId === 'string' ? body.eventId : undefined;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
eventId = undefined;
|
|
27
|
+
}
|
|
28
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
29
|
+
alerts: options.alerts,
|
|
30
|
+
source: options.platform,
|
|
31
|
+
eventId,
|
|
32
|
+
alert: options.alert,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return response;
|
|
18
36
|
}
|
|
19
37
|
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, options.platform, options.secret, options.toleranceInSeconds, options.normalize);
|
|
20
38
|
if (!result.isValid) {
|
|
21
39
|
return Response.json({ error: result.error, errorCode: result.errorCode, platform: result.platform, metadata: result.metadata }, { status: 400 });
|
|
22
40
|
}
|
|
41
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
42
|
+
alerts: options.alerts,
|
|
43
|
+
source: options.platform,
|
|
44
|
+
eventId: result.eventId,
|
|
45
|
+
alert: options.alert,
|
|
46
|
+
});
|
|
23
47
|
const data = await options.handler(result.payload, (result.metadata || {}));
|
|
24
48
|
return Response.json(data);
|
|
25
49
|
}
|
package/dist/alerts.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './notifications';
|
|
2
|
+
export declare const alertInternals: {
|
|
3
|
+
resolveDestinations: typeof import("./notifications/utils").resolveDestinations;
|
|
4
|
+
normalizeAlertOptions: typeof import("./notifications/utils").normalizeAlertOptions;
|
|
5
|
+
buildSlackPayload: typeof import("./notifications/channels/slack").buildSlackPayload;
|
|
6
|
+
buildDiscordPayload: typeof import("./notifications/channels/discord").buildDiscordPayload;
|
|
7
|
+
};
|
package/dist/alerts.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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.alertInternals = void 0;
|
|
18
|
+
const notifications_1 = require("./notifications");
|
|
19
|
+
__exportStar(require("./notifications"), exports);
|
|
20
|
+
// Backward-compatible alias used by previous version internals.
|
|
21
|
+
exports.alertInternals = notifications_1.notificationInternals;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WebhookConfig, WebhookVerificationResult, WebhookPlatform, SignatureConfig, MultiPlatformSecrets, NormalizeOptions } from './types';
|
|
2
2
|
import type { QueueOption } from './upstash/types';
|
|
3
|
+
import type { AlertConfig, SendAlertOptions } from './notifications/types';
|
|
3
4
|
export declare class WebhookVerificationService {
|
|
4
5
|
static verify<TPayload = unknown>(request: Request, config: WebhookConfig): Promise<WebhookVerificationResult<TPayload>>;
|
|
5
6
|
private static getVerifier;
|
|
@@ -20,6 +21,8 @@ export declare class WebhookVerificationService {
|
|
|
20
21
|
platform: WebhookPlatform;
|
|
21
22
|
secret: string;
|
|
22
23
|
queue: QueueOption;
|
|
24
|
+
alerts?: AlertConfig;
|
|
25
|
+
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
|
23
26
|
handler: (payload: unknown, metadata: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
24
27
|
toleranceInSeconds?: number;
|
|
25
28
|
}): Promise<Response>;
|
|
@@ -31,4 +34,5 @@ export { createAlgorithmVerifier } from './verifiers/algorithms';
|
|
|
31
34
|
export { createCustomVerifier } from './verifiers/custom-algorithms';
|
|
32
35
|
export { normalizePayload, getPlatformNormalizationCategory, getPlatformsByCategory, } from './normalization/simple';
|
|
33
36
|
export * from './adapters';
|
|
37
|
+
export * from './alerts';
|
|
34
38
|
export default WebhookVerificationService;
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ const algorithms_1 = require("./verifiers/algorithms");
|
|
|
42
42
|
const custom_algorithms_1 = require("./verifiers/custom-algorithms");
|
|
43
43
|
const algorithms_2 = require("./platforms/algorithms");
|
|
44
44
|
const simple_1 = require("./normalization/simple");
|
|
45
|
+
const dispatch_1 = require("./notifications/dispatch");
|
|
45
46
|
class WebhookVerificationService {
|
|
46
47
|
static async verify(request, config) {
|
|
47
48
|
const verifier = this.getVerifier(config);
|
|
@@ -324,13 +325,30 @@ class WebhookVerificationService {
|
|
|
324
325
|
static async handleWithQueue(request, options) {
|
|
325
326
|
const { resolveQueueConfig, handleQueuedRequest } = await Promise.resolve().then(() => __importStar(require('./upstash/queue')));
|
|
326
327
|
const queueConfig = resolveQueueConfig(options.queue);
|
|
327
|
-
|
|
328
|
+
const response = await handleQueuedRequest(request, {
|
|
328
329
|
platform: options.platform,
|
|
329
330
|
secret: options.secret,
|
|
330
331
|
queueConfig,
|
|
331
332
|
handler: options.handler,
|
|
332
333
|
toleranceInSeconds: options.toleranceInSeconds ?? 300,
|
|
333
334
|
});
|
|
335
|
+
if (response.ok) {
|
|
336
|
+
let eventId;
|
|
337
|
+
try {
|
|
338
|
+
const body = await response.clone().json();
|
|
339
|
+
eventId = typeof body.eventId === 'string' ? body.eventId : undefined;
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
eventId = undefined;
|
|
343
|
+
}
|
|
344
|
+
await (0, dispatch_1.dispatchWebhookAlert)({
|
|
345
|
+
alerts: options.alerts,
|
|
346
|
+
source: options.platform,
|
|
347
|
+
eventId,
|
|
348
|
+
alert: options.alert,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return response;
|
|
334
352
|
}
|
|
335
353
|
static async verifyTokenBased(request, webhookId, webhookToken) {
|
|
336
354
|
return this.verifyTokenAuth(request, webhookId, webhookToken);
|
|
@@ -352,4 +370,5 @@ Object.defineProperty(exports, "normalizePayload", { enumerable: true, get: func
|
|
|
352
370
|
Object.defineProperty(exports, "getPlatformNormalizationCategory", { enumerable: true, get: function () { return simple_2.getPlatformNormalizationCategory; } });
|
|
353
371
|
Object.defineProperty(exports, "getPlatformsByCategory", { enumerable: true, get: function () { return simple_2.getPlatformsByCategory; } });
|
|
354
372
|
__exportStar(require("./adapters"), exports);
|
|
373
|
+
__exportStar(require("./alerts"), exports);
|
|
355
374
|
exports.default = WebhookVerificationService;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AlertPayloadBuilderInput } from "../../types";
|
|
2
|
+
export declare function buildDiscordPayload(input: AlertPayloadBuilderInput): {
|
|
3
|
+
embeds: {
|
|
4
|
+
title: string;
|
|
5
|
+
description: string;
|
|
6
|
+
color: number;
|
|
7
|
+
fields: {
|
|
8
|
+
name: string;
|
|
9
|
+
value: string;
|
|
10
|
+
inline?: boolean;
|
|
11
|
+
}[];
|
|
12
|
+
footer: {
|
|
13
|
+
text: string;
|
|
14
|
+
} | undefined;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDiscordPayload = buildDiscordPayload;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const constants_1 = require("../../constants");
|
|
6
|
+
function buildDiscordPayload(input) {
|
|
7
|
+
const isDLQ = input.dlq;
|
|
8
|
+
const fallbackTitle = isDLQ ? "Dead Letter Queue — Event Failed" : "Webhook Received";
|
|
9
|
+
const title = input.title?.trim() ? input.title : fallbackTitle;
|
|
10
|
+
const fallbackDescription = isDLQ
|
|
11
|
+
? "Event exhausted all retries. Manual replay required."
|
|
12
|
+
: "Event verified and queued for processing.";
|
|
13
|
+
const description = input.message?.trim() ? input.message : fallbackDescription;
|
|
14
|
+
const fields = [];
|
|
15
|
+
if (input.source) {
|
|
16
|
+
fields.push({ name: "Platform", value: input.source, inline: true });
|
|
17
|
+
}
|
|
18
|
+
fields.push({
|
|
19
|
+
name: "Severity",
|
|
20
|
+
value: input.severity.toLowerCase(),
|
|
21
|
+
inline: true,
|
|
22
|
+
});
|
|
23
|
+
if (isDLQ) {
|
|
24
|
+
fields.push({ name: "Queue", value: "dlq", inline: true });
|
|
25
|
+
}
|
|
26
|
+
if (input.eventId) {
|
|
27
|
+
fields.push({
|
|
28
|
+
name: isDLQ ? "DLQ ID" : "Event ID",
|
|
29
|
+
value: `\`${input.eventId}\``,
|
|
30
|
+
inline: false,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const metadataString = (0, utils_1.compactMetadata)(input.metadata);
|
|
34
|
+
if (metadataString) {
|
|
35
|
+
fields.push({ name: "Details", value: `\`\`\`${metadataString}\`\`\`` });
|
|
36
|
+
}
|
|
37
|
+
const replayLine = input.replayUrl
|
|
38
|
+
? `\n\n[${input.replayLabel ?? "Replay Event"}](${input.replayUrl})`
|
|
39
|
+
: "";
|
|
40
|
+
const footerText = input.branding === false ? undefined : "Alert from Tern · tern.hookflo.com";
|
|
41
|
+
return {
|
|
42
|
+
embeds: [
|
|
43
|
+
{
|
|
44
|
+
title,
|
|
45
|
+
description: `${description}${replayLine}`,
|
|
46
|
+
color: parseInt(constants_1.severityColorMap[input.severity].replace("#", ""), 16),
|
|
47
|
+
fields,
|
|
48
|
+
footer: footerText ? { text: footerText } : undefined,
|
|
49
|
+
timestamp: new Date().toISOString(),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { buildDiscordPayload } from './build-payload';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDiscordPayload = void 0;
|
|
4
|
+
var build_payload_1 = require("./build-payload");
|
|
5
|
+
Object.defineProperty(exports, "buildDiscordPayload", { enumerable: true, get: function () { return build_payload_1.buildDiscordPayload; } });
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSlackPayload = buildSlackPayload;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const constants_1 = require("../../constants");
|
|
6
|
+
function buildSlackPayload(input) {
|
|
7
|
+
const isDLQ = input.dlq;
|
|
8
|
+
const fallbackTitle = isDLQ ? "Dead Letter Queue — Event Failed" : "Webhook Received";
|
|
9
|
+
const title = input.title?.trim() ? input.title : fallbackTitle;
|
|
10
|
+
const fallbackMessage = isDLQ
|
|
11
|
+
? "Event exhausted all retries. Manual replay required."
|
|
12
|
+
: "Event verified and queued for processing.";
|
|
13
|
+
const message = input.message?.trim() ? input.message : fallbackMessage;
|
|
14
|
+
const fields = [
|
|
15
|
+
input.source
|
|
16
|
+
? { type: "mrkdwn", text: `*Platform*\n${input.source}` }
|
|
17
|
+
: null,
|
|
18
|
+
{
|
|
19
|
+
type: "mrkdwn",
|
|
20
|
+
text: `*Severity*\n${input.severity.toLowerCase()}`,
|
|
21
|
+
},
|
|
22
|
+
isDLQ ? { type: "mrkdwn", text: "*Queue*\ndlq" } : null,
|
|
23
|
+
input.eventId
|
|
24
|
+
? {
|
|
25
|
+
type: "mrkdwn",
|
|
26
|
+
text: `*${isDLQ ? "DLQ ID" : "Event ID"}*\n\`${input.eventId}\``,
|
|
27
|
+
}
|
|
28
|
+
: null,
|
|
29
|
+
].filter(Boolean);
|
|
30
|
+
const blocks = [
|
|
31
|
+
{
|
|
32
|
+
type: "section",
|
|
33
|
+
text: {
|
|
34
|
+
type: "mrkdwn",
|
|
35
|
+
text: `*${title}*\n${message}`,
|
|
36
|
+
},
|
|
37
|
+
fields,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
const metadataString = (0, utils_1.compactMetadata)(input.metadata);
|
|
41
|
+
if (metadataString) {
|
|
42
|
+
blocks.push({
|
|
43
|
+
type: "section",
|
|
44
|
+
text: {
|
|
45
|
+
type: "mrkdwn",
|
|
46
|
+
text: `\`\`\`${metadataString}\`\`\``,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (input.replayUrl) {
|
|
51
|
+
blocks.push({
|
|
52
|
+
type: "actions",
|
|
53
|
+
elements: [
|
|
54
|
+
{
|
|
55
|
+
type: "button",
|
|
56
|
+
text: {
|
|
57
|
+
type: "plain_text",
|
|
58
|
+
text: input.replayLabel ?? "Replay Event",
|
|
59
|
+
emoji: false,
|
|
60
|
+
},
|
|
61
|
+
url: input.replayUrl,
|
|
62
|
+
style: "danger",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (input.branding !== false) {
|
|
68
|
+
blocks.push({
|
|
69
|
+
type: "context",
|
|
70
|
+
elements: [
|
|
71
|
+
{
|
|
72
|
+
type: "mrkdwn",
|
|
73
|
+
text: `Alert from <${constants_1.TERN_BRAND_URL}|Tern>`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
text: title,
|
|
80
|
+
blocks,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { buildSlackPayload } from './build-payload';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSlackPayload = void 0;
|
|
4
|
+
var build_payload_1 = require("./build-payload");
|
|
5
|
+
Object.defineProperty(exports, "buildSlackPayload", { enumerable: true, get: function () { return build_payload_1.buildSlackPayload; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AlertSeverity } from './types';
|
|
2
|
+
export declare const TERN_BRAND_URL = "https://tern.hookflo.com";
|
|
3
|
+
export declare const DEFAULT_DLQ_TITLE = "Dead Letter Queue \u2014 Event Failed";
|
|
4
|
+
export declare const DEFAULT_ALERT_TITLE = "Webhook Received";
|
|
5
|
+
export declare const DEFAULT_REPLAY_LABEL = "Replay DLQ Event";
|
|
6
|
+
export declare const DEFAULT_DLQ_MESSAGE = "Event exhausted all retries. Manual replay required.";
|
|
7
|
+
export declare const DEFAULT_ALERT_MESSAGE = "Event verified and queued for processing.";
|
|
8
|
+
export declare const severityColorMap: Record<AlertSeverity, string>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.severityColorMap = exports.DEFAULT_ALERT_MESSAGE = exports.DEFAULT_DLQ_MESSAGE = exports.DEFAULT_REPLAY_LABEL = exports.DEFAULT_ALERT_TITLE = exports.DEFAULT_DLQ_TITLE = exports.TERN_BRAND_URL = void 0;
|
|
4
|
+
exports.TERN_BRAND_URL = 'https://tern.hookflo.com';
|
|
5
|
+
exports.DEFAULT_DLQ_TITLE = 'Dead Letter Queue — Event Failed';
|
|
6
|
+
exports.DEFAULT_ALERT_TITLE = 'Webhook Received';
|
|
7
|
+
exports.DEFAULT_REPLAY_LABEL = 'Replay DLQ Event';
|
|
8
|
+
exports.DEFAULT_DLQ_MESSAGE = 'Event exhausted all retries. Manual replay required.';
|
|
9
|
+
exports.DEFAULT_ALERT_MESSAGE = 'Event verified and queued for processing.';
|
|
10
|
+
exports.severityColorMap = {
|
|
11
|
+
info: '#3B82F6',
|
|
12
|
+
warning: '#F59E0B',
|
|
13
|
+
error: '#EF4444',
|
|
14
|
+
critical: '#7C3AED',
|
|
15
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AlertConfig, SendAlertOptions } from './types';
|
|
2
|
+
export interface DispatchWebhookAlertInput {
|
|
3
|
+
alerts?: AlertConfig;
|
|
4
|
+
source?: string;
|
|
5
|
+
eventId?: string;
|
|
6
|
+
alert?: Omit<SendAlertOptions, 'dlq' | 'dlqId' | 'source' | 'eventId'>;
|
|
7
|
+
}
|
|
8
|
+
export declare function dispatchWebhookAlert(input: DispatchWebhookAlertInput): Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dispatchWebhookAlert = dispatchWebhookAlert;
|
|
4
|
+
const send_alert_1 = require("./send-alert");
|
|
5
|
+
async function dispatchWebhookAlert(input) {
|
|
6
|
+
if (!input.alerts)
|
|
7
|
+
return;
|
|
8
|
+
await (0, send_alert_1.sendAlert)(input.alerts, {
|
|
9
|
+
...(input.alert || {}),
|
|
10
|
+
source: input.source,
|
|
11
|
+
eventId: input.eventId,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { normalizeAlertOptions, resolveDestinations } from './utils';
|
|
2
|
+
import { buildSlackPayload } from './channels/slack';
|
|
3
|
+
import { buildDiscordPayload } from './channels/discord';
|
|
4
|
+
export * from './types';
|
|
5
|
+
export declare const notificationInternals: {
|
|
6
|
+
resolveDestinations: typeof resolveDestinations;
|
|
7
|
+
normalizeAlertOptions: typeof normalizeAlertOptions;
|
|
8
|
+
buildSlackPayload: typeof buildSlackPayload;
|
|
9
|
+
buildDiscordPayload: typeof buildDiscordPayload;
|
|
10
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
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.notificationInternals = void 0;
|
|
18
|
+
const utils_1 = require("./utils");
|
|
19
|
+
const slack_1 = require("./channels/slack");
|
|
20
|
+
const discord_1 = require("./channels/discord");
|
|
21
|
+
__exportStar(require("./types"), exports);
|
|
22
|
+
exports.notificationInternals = {
|
|
23
|
+
resolveDestinations: utils_1.resolveDestinations,
|
|
24
|
+
normalizeAlertOptions: utils_1.normalizeAlertOptions,
|
|
25
|
+
buildSlackPayload: slack_1.buildSlackPayload,
|
|
26
|
+
buildDiscordPayload: discord_1.buildDiscordPayload,
|
|
27
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendAlert = sendAlert;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
const slack_1 = require("./channels/slack");
|
|
6
|
+
const discord_1 = require("./channels/discord");
|
|
7
|
+
async function postWebhook(webhookUrl, body) {
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch(webhookUrl, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
'content-type': 'application/json',
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify(body),
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
status: response.status,
|
|
20
|
+
error: `Webhook call failed with ${response.status}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { ok: true, status: response.status };
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
error: error.message,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function buildPayload(channel, options) {
|
|
33
|
+
const normalized = (0, utils_1.normalizeAlertOptions)(options);
|
|
34
|
+
return channel === 'slack' ? (0, slack_1.buildSlackPayload)(normalized) : (0, discord_1.buildDiscordPayload)(normalized);
|
|
35
|
+
}
|
|
36
|
+
async function sendAlert(config, options) {
|
|
37
|
+
const destinations = (0, utils_1.resolveDestinations)(config);
|
|
38
|
+
if (destinations.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
total: 0,
|
|
42
|
+
delivered: 0,
|
|
43
|
+
results: [{
|
|
44
|
+
channel: 'slack',
|
|
45
|
+
webhookUrl: '',
|
|
46
|
+
ok: false,
|
|
47
|
+
error: 'No valid alert webhook destinations configured',
|
|
48
|
+
}],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const results = await Promise.all(destinations.map(async (destination) => {
|
|
52
|
+
const payload = buildPayload(destination.channel, options);
|
|
53
|
+
const response = await postWebhook(destination.webhookUrl, payload);
|
|
54
|
+
return {
|
|
55
|
+
channel: destination.channel,
|
|
56
|
+
webhookUrl: destination.webhookUrl,
|
|
57
|
+
ok: response.ok,
|
|
58
|
+
status: response.status,
|
|
59
|
+
error: response.error,
|
|
60
|
+
};
|
|
61
|
+
}));
|
|
62
|
+
const delivered = results.filter((result) => result.ok).length;
|
|
63
|
+
return {
|
|
64
|
+
success: delivered === results.length,
|
|
65
|
+
total: results.length,
|
|
66
|
+
delivered,
|
|
67
|
+
results,
|
|
68
|
+
};
|
|
69
|
+
}
|