@futurewindai/wotchi 0.1.0-beta.1

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.
Files changed (89) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +128 -0
  3. package/SECURITY.md +17 -0
  4. package/dist/cjs/core/client.js +154 -0
  5. package/dist/cjs/core/config.js +114 -0
  6. package/dist/cjs/core/diagnostics.js +14 -0
  7. package/dist/cjs/core/errors.js +11 -0
  8. package/dist/cjs/core/fingerprint.js +37 -0
  9. package/dist/cjs/core/group-store.js +95 -0
  10. package/dist/cjs/core/incident-builder.js +46 -0
  11. package/dist/cjs/core/incident-policy.js +31 -0
  12. package/dist/cjs/core/normalize.js +138 -0
  13. package/dist/cjs/core/notification-queue.js +103 -0
  14. package/dist/cjs/core/process-monitor.js +30 -0
  15. package/dist/cjs/core/redact.js +141 -0
  16. package/dist/cjs/core/rolling-window.js +60 -0
  17. package/dist/cjs/core/stack-frame.js +33 -0
  18. package/dist/cjs/core/types.js +2 -0
  19. package/dist/cjs/index.js +13 -0
  20. package/dist/cjs/integrations/express/error-handler.js +15 -0
  21. package/dist/cjs/integrations/express/index.js +12 -0
  22. package/dist/cjs/integrations/express/request-context.js +19 -0
  23. package/dist/cjs/integrations/nest/exception-filter.js +36 -0
  24. package/dist/cjs/integrations/nest/index.js +9 -0
  25. package/dist/cjs/integrations/nest/register.js +13 -0
  26. package/dist/cjs/integrations/nest/request-context.js +41 -0
  27. package/dist/cjs/integrations/request-context.js +76 -0
  28. package/dist/cjs/notifiers/console.js +33 -0
  29. package/dist/cjs/notifiers/telegram-format.js +52 -0
  30. package/dist/cjs/notifiers/telegram-http.js +150 -0
  31. package/dist/cjs/notifiers/telegram.js +38 -0
  32. package/dist/cjs/package.json +1 -0
  33. package/dist/esm/core/client.js +151 -0
  34. package/dist/esm/core/config.js +110 -0
  35. package/dist/esm/core/diagnostics.js +10 -0
  36. package/dist/esm/core/errors.js +7 -0
  37. package/dist/esm/core/fingerprint.js +33 -0
  38. package/dist/esm/core/group-store.js +92 -0
  39. package/dist/esm/core/incident-builder.js +43 -0
  40. package/dist/esm/core/incident-policy.js +28 -0
  41. package/dist/esm/core/normalize.js +134 -0
  42. package/dist/esm/core/notification-queue.js +100 -0
  43. package/dist/esm/core/process-monitor.js +27 -0
  44. package/dist/esm/core/redact.js +136 -0
  45. package/dist/esm/core/rolling-window.js +57 -0
  46. package/dist/esm/core/stack-frame.js +29 -0
  47. package/dist/esm/core/types.js +1 -0
  48. package/dist/esm/index.js +6 -0
  49. package/dist/esm/integrations/express/error-handler.js +12 -0
  50. package/dist/esm/integrations/express/index.js +5 -0
  51. package/dist/esm/integrations/express/request-context.js +16 -0
  52. package/dist/esm/integrations/nest/exception-filter.js +33 -0
  53. package/dist/esm/integrations/nest/index.js +2 -0
  54. package/dist/esm/integrations/nest/register.js +10 -0
  55. package/dist/esm/integrations/nest/request-context.js +38 -0
  56. package/dist/esm/integrations/request-context.js +72 -0
  57. package/dist/esm/notifiers/console.js +29 -0
  58. package/dist/esm/notifiers/telegram-format.js +49 -0
  59. package/dist/esm/notifiers/telegram-http.js +144 -0
  60. package/dist/esm/notifiers/telegram.js +34 -0
  61. package/dist/types/core/client.d.ts +2 -0
  62. package/dist/types/core/config.d.ts +29 -0
  63. package/dist/types/core/diagnostics.d.ts +7 -0
  64. package/dist/types/core/errors.d.ts +3 -0
  65. package/dist/types/core/fingerprint.d.ts +3 -0
  66. package/dist/types/core/group-store.d.ts +15 -0
  67. package/dist/types/core/incident-builder.d.ts +3 -0
  68. package/dist/types/core/incident-policy.d.ts +15 -0
  69. package/dist/types/core/normalize.d.ts +17 -0
  70. package/dist/types/core/notification-queue.d.ts +16 -0
  71. package/dist/types/core/process-monitor.d.ts +5 -0
  72. package/dist/types/core/redact.d.ts +11 -0
  73. package/dist/types/core/rolling-window.d.ts +12 -0
  74. package/dist/types/core/stack-frame.d.ts +2 -0
  75. package/dist/types/core/types.d.ts +109 -0
  76. package/dist/types/index.d.ts +9 -0
  77. package/dist/types/integrations/express/error-handler.d.ts +4 -0
  78. package/dist/types/integrations/express/index.d.ts +7 -0
  79. package/dist/types/integrations/express/request-context.d.ts +5 -0
  80. package/dist/types/integrations/nest/exception-filter.d.ts +10 -0
  81. package/dist/types/integrations/nest/index.d.ts +4 -0
  82. package/dist/types/integrations/nest/register.d.ts +10 -0
  83. package/dist/types/integrations/nest/request-context.d.ts +5 -0
  84. package/dist/types/integrations/request-context.d.ts +13 -0
  85. package/dist/types/notifiers/console.d.ts +3 -0
  86. package/dist/types/notifiers/telegram-format.d.ts +2 -0
  87. package/dist/types/notifiers/telegram-http.d.ts +21 -0
  88. package/dist/types/notifiers/telegram.d.ts +4 -0
  89. package/package.json +111 -0
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeStackFrame = normalizeStackFrame;
4
+ exports.selectApplicationFrame = selectApplicationFrame;
5
+ const INTERNAL_MARKERS = ["node_modules/", "node:internal/", "internal/"];
6
+ const isInternalFrame = (frame) => {
7
+ const lower = frame.toLowerCase();
8
+ return INTERNAL_MARKERS.some((marker) => lower.includes(marker));
9
+ };
10
+ function normalizeStackFrame(frame) {
11
+ return frame
12
+ .trim()
13
+ .replace(/^at\s+/, "")
14
+ .replace(/:\d+:\d+(?=\)?$)/, "")
15
+ .replace(/\s+/g, " ");
16
+ }
17
+ function selectApplicationFrame(stack) {
18
+ if (typeof stack !== "string" || stack.length === 0) {
19
+ return undefined;
20
+ }
21
+ for (const line of stack.split("\n")) {
22
+ const trimmed = line.trim();
23
+ if (!trimmed || /^\w*Error(?::|$)/.test(trimmed)) {
24
+ continue;
25
+ }
26
+ const frame = normalizeStackFrame(trimmed);
27
+ if (!frame || isInternalFrame(frame)) {
28
+ continue;
29
+ }
30
+ return frame;
31
+ }
32
+ return undefined;
33
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.telegramNotifier = exports.registerWotchiProcessMonitor = exports.createWotchi = exports.consoleNotifier = exports.WotchiConfigurationError = void 0;
4
+ const client_js_1 = require("./core/client.js");
5
+ Object.defineProperty(exports, "createWotchi", { enumerable: true, get: function () { return client_js_1.createWotchi; } });
6
+ const process_monitor_js_1 = require("./core/process-monitor.js");
7
+ Object.defineProperty(exports, "registerWotchiProcessMonitor", { enumerable: true, get: function () { return process_monitor_js_1.registerWotchiProcessMonitor; } });
8
+ const console_js_1 = require("./notifiers/console.js");
9
+ Object.defineProperty(exports, "consoleNotifier", { enumerable: true, get: function () { return console_js_1.consoleNotifier; } });
10
+ const telegram_js_1 = require("./notifiers/telegram.js");
11
+ Object.defineProperty(exports, "telegramNotifier", { enumerable: true, get: function () { return telegram_js_1.telegramNotifier; } });
12
+ var errors_js_1 = require("./core/errors.js");
13
+ Object.defineProperty(exports, "WotchiConfigurationError", { enumerable: true, get: function () { return errors_js_1.WotchiConfigurationError; } });
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createExpressErrorHandler = createExpressErrorHandler;
4
+ const request_context_js_1 = require("./request-context.js");
5
+ function createExpressErrorHandler(client, options) {
6
+ return (error, request, response, next) => {
7
+ try {
8
+ client.captureException(error, (0, request_context_js_1.getExpressRequestContext)(request, response, options));
9
+ }
10
+ catch {
11
+ // A capture failure must never change Express error handling.
12
+ }
13
+ next(error);
14
+ };
15
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.telegramNotifier = exports.createWotchi = exports.consoleNotifier = void 0;
4
+ exports.wotchiErrorHandler = wotchiErrorHandler;
5
+ const error_handler_js_1 = require("./error-handler.js");
6
+ var index_js_1 = require("../../index.js");
7
+ Object.defineProperty(exports, "consoleNotifier", { enumerable: true, get: function () { return index_js_1.consoleNotifier; } });
8
+ Object.defineProperty(exports, "createWotchi", { enumerable: true, get: function () { return index_js_1.createWotchi; } });
9
+ Object.defineProperty(exports, "telegramNotifier", { enumerable: true, get: function () { return index_js_1.telegramNotifier; } });
10
+ function wotchiErrorHandler(client, options) {
11
+ return (0, error_handler_js_1.createExpressErrorHandler)(client, options);
12
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getExpressRequestContext = getExpressRequestContext;
4
+ const request_context_js_1 = require("../request-context.js");
5
+ function getExpressRequestContext(request, response, options) {
6
+ try {
7
+ const route = request.route?.path;
8
+ return (0, request_context_js_1.buildRequestContext)({
9
+ request,
10
+ method: request.method,
11
+ route: route ?? request.path,
12
+ statusCode: response.statusCode,
13
+ ...(options === undefined ? {} : { options }),
14
+ });
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.WotchiNestExceptionFilter = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const core_1 = require("@nestjs/core");
15
+ const request_context_js_1 = require("./request-context.js");
16
+ let WotchiNestExceptionFilter = class WotchiNestExceptionFilter extends core_1.BaseExceptionFilter {
17
+ constructor(client, applicationRef, options) {
18
+ super(applicationRef);
19
+ this.client = client;
20
+ this.options = options;
21
+ }
22
+ catch(exception, host) {
23
+ try {
24
+ this.client.captureException(exception, (0, request_context_js_1.getNestRequestContext)(host, exception, this.options));
25
+ }
26
+ catch {
27
+ // A capture failure must never change NestJS exception handling.
28
+ }
29
+ super.catch(exception, host);
30
+ }
31
+ };
32
+ exports.WotchiNestExceptionFilter = WotchiNestExceptionFilter;
33
+ exports.WotchiNestExceptionFilter = WotchiNestExceptionFilter = __decorate([
34
+ (0, common_1.Catch)(),
35
+ __metadata("design:paramtypes", [Object, Object, Object])
36
+ ], WotchiNestExceptionFilter);
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerWotchiNest = exports.telegramNotifier = exports.createWotchi = exports.consoleNotifier = void 0;
4
+ var index_js_1 = require("../../index.js");
5
+ Object.defineProperty(exports, "consoleNotifier", { enumerable: true, get: function () { return index_js_1.consoleNotifier; } });
6
+ Object.defineProperty(exports, "createWotchi", { enumerable: true, get: function () { return index_js_1.createWotchi; } });
7
+ Object.defineProperty(exports, "telegramNotifier", { enumerable: true, get: function () { return index_js_1.telegramNotifier; } });
8
+ var register_js_1 = require("./register.js");
9
+ Object.defineProperty(exports, "registerWotchiNest", { enumerable: true, get: function () { return register_js_1.registerWotchiNest; } });
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerWotchiNest = registerWotchiNest;
4
+ const core_1 = require("@nestjs/core");
5
+ const exception_filter_js_1 = require("./exception-filter.js");
6
+ function registerWotchiNest(app, client, options) {
7
+ const application = app;
8
+ const directAdapter = application.getHttpAdapter?.();
9
+ const httpAdapter = directAdapter === undefined
10
+ ? application.get(core_1.HttpAdapterHost).httpAdapter
11
+ : directAdapter;
12
+ application.useGlobalFilters(new exception_filter_js_1.WotchiNestExceptionFilter(client, httpAdapter, options));
13
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNestRequestContext = getNestRequestContext;
4
+ const request_context_js_1 = require("../request-context.js");
5
+ const isHttpException = (exception) => typeof exception === "object" &&
6
+ exception !== null &&
7
+ typeof exception.getStatus === "function";
8
+ const getStatusCode = (exception, response) => {
9
+ if (isHttpException(exception)) {
10
+ try {
11
+ const status = exception.getStatus();
12
+ return Number.isSafeInteger(status) ? status : undefined;
13
+ }
14
+ catch {
15
+ return undefined;
16
+ }
17
+ }
18
+ return typeof response.statusCode === "number" &&
19
+ Number.isSafeInteger(response.statusCode) &&
20
+ response.statusCode >= 400
21
+ ? response.statusCode
22
+ : 500;
23
+ };
24
+ function getNestRequestContext(host, exception, options) {
25
+ try {
26
+ const httpHost = host.switchToHttp();
27
+ const request = httpHost.getRequest();
28
+ const response = httpHost.getResponse();
29
+ const route = request.route?.path ?? request.path ?? request.url;
30
+ return (0, request_context_js_1.buildRequestContext)({
31
+ request,
32
+ method: request.method,
33
+ route,
34
+ statusCode: getStatusCode(exception, response),
35
+ ...(options === undefined ? {} : { options }),
36
+ });
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeRoutePath = normalizeRoutePath;
4
+ exports.buildRequestContext = buildRequestContext;
5
+ const MAX_ROUTE_LENGTH = 500;
6
+ const MAX_REQUEST_ID_LENGTH = 200;
7
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
8
+ const HEX_PATTERN = /^[0-9a-f]{8,}$/i;
9
+ const isRecord = (value) => typeof value === "object" && value !== null;
10
+ const dynamicSegment = (segment) => /^\d+$/.test(segment) ||
11
+ UUID_PATTERN.test(segment) ||
12
+ HEX_PATTERN.test(segment) ||
13
+ segment.length >= 24
14
+ ? ":id"
15
+ : segment;
16
+ function normalizeRoutePath(value) {
17
+ if (typeof value !== "string") {
18
+ return undefined;
19
+ }
20
+ const path = value.split("?", 1)[0]?.trim();
21
+ if (path === undefined || path.length === 0) {
22
+ return undefined;
23
+ }
24
+ return path.split("/").map(dynamicSegment).join("/").slice(0, MAX_ROUTE_LENGTH);
25
+ }
26
+ const normalizeRequestIdProperty = (property) => {
27
+ if (property === undefined) {
28
+ return undefined;
29
+ }
30
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(property)) {
31
+ throw new TypeError("requestIdProperty must be a simple property name");
32
+ }
33
+ return property;
34
+ };
35
+ const readRequestId = (request, property) => {
36
+ if (property === undefined || !isRecord(request)) {
37
+ return undefined;
38
+ }
39
+ try {
40
+ if (!Object.prototype.hasOwnProperty.call(request, property)) {
41
+ return undefined;
42
+ }
43
+ const value = request[property];
44
+ return typeof value === "string" ? value.slice(0, MAX_REQUEST_ID_LENGTH) : undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ };
50
+ function buildRequestContext(input) {
51
+ const requestIdProperty = normalizeRequestIdProperty(input.options?.requestIdProperty);
52
+ let requestId;
53
+ try {
54
+ requestId = readRequestId(input.request, requestIdProperty);
55
+ }
56
+ catch {
57
+ requestId = undefined;
58
+ }
59
+ const method = typeof input.method === "string" ? input.method.slice(0, 16) : undefined;
60
+ const route = normalizeRoutePath(input.route);
61
+ const statusCode = typeof input.statusCode === "number" && Number.isSafeInteger(input.statusCode)
62
+ ? input.statusCode
63
+ : undefined;
64
+ if (method === undefined &&
65
+ route === undefined &&
66
+ statusCode === undefined &&
67
+ requestId === undefined) {
68
+ return undefined;
69
+ }
70
+ return {
71
+ ...(method === undefined ? {} : { method }),
72
+ ...(route === undefined ? {} : { route }),
73
+ ...(statusCode === undefined ? {} : { statusCode }),
74
+ ...(requestId === undefined ? {} : { requestId }),
75
+ };
76
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatConsoleAlert = formatConsoleAlert;
4
+ exports.consoleNotifier = consoleNotifier;
5
+ const MAX_FIELD_LENGTH = 1_000;
6
+ const MAX_ACTIONS = 5;
7
+ const limit = (value, maxLength = MAX_FIELD_LENGTH) => value.slice(0, maxLength);
8
+ function formatConsoleAlert(alert) {
9
+ const actions = alert.suggestedActions
10
+ .slice(0, MAX_ACTIONS)
11
+ .map((action) => `- ${limit(action, 300)}`)
12
+ .join("\n");
13
+ return [
14
+ limit(alert.title),
15
+ `Service: ${limit(alert.service, 300)}`,
16
+ `Environment: ${limit(alert.environment, 300)}`,
17
+ `Summary: ${limit(alert.summary)}`,
18
+ `Occurrences: ${alert.occurrences}`,
19
+ `First seen: ${limit(alert.firstSeenAt, 100)}`,
20
+ `Last seen: ${limit(alert.lastSeenAt, 100)}`,
21
+ "Suggested checks:",
22
+ actions,
23
+ ].join("\n");
24
+ }
25
+ function consoleNotifier(options) {
26
+ const write = options?.write ?? ((line) => console.error(line));
27
+ return {
28
+ name: "console",
29
+ async send(alert) {
30
+ write(formatConsoleAlert(alert));
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTelegramAlert = formatTelegramAlert;
4
+ const MAX_MESSAGE_LENGTH = 4_096;
5
+ const MAX_ACTIONS = 5;
6
+ const MAX_TITLE_LENGTH = 300;
7
+ const MAX_SERVICE_LENGTH = 300;
8
+ const MAX_ENVIRONMENT_LENGTH = 300;
9
+ const MAX_SUMMARY_LENGTH = 2_000;
10
+ const MAX_ACTION_LENGTH = 600;
11
+ const escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
12
+ const limit = (value, maxLength) => value.slice(0, maxLength);
13
+ const render = (title, service, environment, summary, actions, firstSeenAt, lastSeenAt, occurrences) => {
14
+ const actionText = actions.length === 0
15
+ ? "- No suggested checks."
16
+ : actions.map((action) => `- ${escapeHtml(action)}`).join("\n");
17
+ return [
18
+ `<b>${escapeHtml(title)}</b>`,
19
+ `Service: ${escapeHtml(service)}`,
20
+ `Environment: ${escapeHtml(environment)}`,
21
+ `Summary: ${escapeHtml(summary)}`,
22
+ `Occurrences: ${occurrences}`,
23
+ `First seen: ${escapeHtml(firstSeenAt)}`,
24
+ `Last seen: ${escapeHtml(lastSeenAt)}`,
25
+ "Suggested checks:",
26
+ actionText,
27
+ ].join("\n");
28
+ };
29
+ function formatTelegramAlert(alert) {
30
+ const title = limit(alert.title, MAX_TITLE_LENGTH);
31
+ const service = limit(alert.service, MAX_SERVICE_LENGTH);
32
+ const environment = limit(alert.environment, MAX_ENVIRONMENT_LENGTH);
33
+ const firstSeenAt = limit(alert.firstSeenAt, 100);
34
+ const lastSeenAt = limit(alert.lastSeenAt, 100);
35
+ let summary = limit(alert.summary, MAX_SUMMARY_LENGTH);
36
+ let actions = alert.suggestedActions
37
+ .slice(0, MAX_ACTIONS)
38
+ .map((action) => limit(action, MAX_ACTION_LENGTH));
39
+ let formatted = render(title, service, environment, summary, actions, firstSeenAt, lastSeenAt, alert.occurrences);
40
+ while (formatted.length > MAX_MESSAGE_LENGTH && actions.length > 0) {
41
+ actions = actions.slice(0, -1);
42
+ formatted = render(title, service, environment, summary, actions, firstSeenAt, lastSeenAt, alert.occurrences);
43
+ }
44
+ while (formatted.length > MAX_MESSAGE_LENGTH && summary.length > 0) {
45
+ summary = summary.slice(0, Math.max(0, summary.length - 200));
46
+ formatted = render(title, service, environment, summary, actions, firstSeenAt, lastSeenAt, alert.occurrences);
47
+ }
48
+ if (formatted.length > MAX_MESSAGE_LENGTH) {
49
+ formatted = render(limit(title, 100), limit(service, 100), limit(environment, 100), "Incident details were truncated.", [], firstSeenAt, lastSeenAt, alert.occurrences);
50
+ }
51
+ return formatted;
52
+ }
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sendTelegramMessage = sendTelegramMessage;
7
+ const node_https_1 = __importDefault(require("node:https"));
8
+ const DEFAULT_TIMEOUT_MS = 3_000;
9
+ const MAX_RESPONSE_BYTES = 8_192;
10
+ const MAX_RETRY_DELAY_MS = 1_000;
11
+ const wait = (delayMs) => new Promise((resolve) => {
12
+ setTimeout(resolve, delayMs);
13
+ });
14
+ const failRequest = () => {
15
+ throw new Error("Telegram request failed");
16
+ };
17
+ const parseRetryAfter = (body) => {
18
+ try {
19
+ const parsed = JSON.parse(body);
20
+ if (typeof parsed !== "object" || parsed === null) {
21
+ return 0;
22
+ }
23
+ const parameters = parsed.parameters;
24
+ if (typeof parameters !== "object" || parameters === null) {
25
+ return 0;
26
+ }
27
+ const retryAfter = parameters.retry_after;
28
+ return typeof retryAfter === "number" && Number.isSafeInteger(retryAfter) && retryAfter > 0
29
+ ? Math.min(retryAfter * 1_000, MAX_RETRY_DELAY_MS)
30
+ : 0;
31
+ }
32
+ catch {
33
+ return 0;
34
+ }
35
+ };
36
+ const isSuccessResponse = (response) => {
37
+ if (response.statusCode < 200 || response.statusCode >= 300) {
38
+ return false;
39
+ }
40
+ try {
41
+ const parsed = JSON.parse(response.body);
42
+ return (typeof parsed === "object" && parsed !== null && parsed.ok === true);
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ };
48
+ const createRequestOptions = (botToken) => ({
49
+ protocol: "https:",
50
+ hostname: "api.telegram.org",
51
+ method: "POST",
52
+ path: `/bot${botToken}/sendMessage`,
53
+ headers: {},
54
+ });
55
+ const httpsRequest = (options, body, timeoutMs) => new Promise((resolve, reject) => {
56
+ let settled = false;
57
+ const finish = (callback) => {
58
+ if (settled) {
59
+ return;
60
+ }
61
+ settled = true;
62
+ callback();
63
+ };
64
+ const request = node_https_1.default.request(options, (response) => {
65
+ const chunks = [];
66
+ let size = 0;
67
+ response.on("data", (chunk) => {
68
+ size += Buffer.byteLength(chunk);
69
+ if (size > MAX_RESPONSE_BYTES) {
70
+ request.destroy();
71
+ finish(() => reject(new Error("Telegram response body exceeded limit")));
72
+ return;
73
+ }
74
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
75
+ });
76
+ response.once("error", (error) => finish(() => reject(error)));
77
+ response.once("end", () => finish(() => resolve({
78
+ statusCode: response.statusCode ?? 0,
79
+ headers: response.headers,
80
+ body: Buffer.concat(chunks).toString("utf8"),
81
+ })));
82
+ });
83
+ request.once("error", (error) => finish(() => reject(error)));
84
+ request.setTimeout(timeoutMs, () => {
85
+ request.destroy();
86
+ finish(() => reject(new Error("Telegram request timed out")));
87
+ });
88
+ request.end(body);
89
+ });
90
+ const requestWithTimeout = async (request, options, body, timeoutMs) => {
91
+ let timer;
92
+ try {
93
+ const timeout = new Promise((_, reject) => {
94
+ timer = setTimeout(() => reject(new Error("Telegram request timed out")), timeoutMs);
95
+ });
96
+ return await Promise.race([request(options, body, timeoutMs), timeout]);
97
+ }
98
+ catch (error) {
99
+ if (error instanceof Error && error.message === "Telegram request timed out") {
100
+ throw error;
101
+ }
102
+ if (error instanceof Error && error.message === "Telegram response body exceeded limit") {
103
+ throw error;
104
+ }
105
+ return failRequest();
106
+ }
107
+ finally {
108
+ if (timer !== undefined) {
109
+ clearTimeout(timer);
110
+ }
111
+ }
112
+ };
113
+ async function sendTelegramMessage(options, request = httpsRequest) {
114
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
115
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 30_000) {
116
+ throw new TypeError("timeoutMs is invalid");
117
+ }
118
+ const body = JSON.stringify({
119
+ chat_id: options.chatId,
120
+ text: options.text,
121
+ parse_mode: "HTML",
122
+ disable_web_page_preview: true,
123
+ });
124
+ const requestOptions = createRequestOptions(options.botToken);
125
+ requestOptions.headers = {
126
+ "content-type": "application/json",
127
+ "content-length": Buffer.byteLength(body, "utf8"),
128
+ };
129
+ let rateLimitRetries = 0;
130
+ let serverRetries = 0;
131
+ while (true) {
132
+ const response = await requestWithTimeout(request, requestOptions, body, timeoutMs);
133
+ if (Buffer.byteLength(response.body, "utf8") > MAX_RESPONSE_BYTES) {
134
+ throw new Error("Telegram response body exceeded limit");
135
+ }
136
+ if (isSuccessResponse(response)) {
137
+ return;
138
+ }
139
+ if (response.statusCode === 429 && rateLimitRetries === 0) {
140
+ rateLimitRetries += 1;
141
+ await wait(parseRetryAfter(response.body));
142
+ continue;
143
+ }
144
+ if (response.statusCode >= 500 && response.statusCode <= 599 && serverRetries === 0) {
145
+ serverRetries += 1;
146
+ continue;
147
+ }
148
+ throw new Error(`Telegram API request failed with status ${response.statusCode}`);
149
+ }
150
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTelegramNotifier = createTelegramNotifier;
4
+ exports.telegramNotifier = telegramNotifier;
5
+ const telegram_format_js_1 = require("./telegram-format.js");
6
+ const telegram_http_js_1 = require("./telegram-http.js");
7
+ const validateValue = (value, field) => {
8
+ if (typeof value !== "string" ||
9
+ value.trim().length === 0 ||
10
+ value.length > 200 ||
11
+ /[\s/?#]/.test(value)) {
12
+ throw new TypeError(`${field} is invalid`);
13
+ }
14
+ return value.trim();
15
+ };
16
+ function createTelegramNotifier(options, request) {
17
+ const botToken = validateValue(options.botToken, "botToken");
18
+ const chatId = validateValue(options.chatId, "chatId");
19
+ const sendOptions = {
20
+ botToken,
21
+ chatId,
22
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
23
+ };
24
+ return {
25
+ name: "telegram",
26
+ async send(alert) {
27
+ const message = { ...sendOptions, text: (0, telegram_format_js_1.formatTelegramAlert)(alert) };
28
+ if (request === undefined) {
29
+ await (0, telegram_http_js_1.sendTelegramMessage)(message);
30
+ return;
31
+ }
32
+ await (0, telegram_http_js_1.sendTelegramMessage)(message, request);
33
+ },
34
+ };
35
+ }
36
+ function telegramNotifier(options) {
37
+ return createTelegramNotifier(options);
38
+ }
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}