botforje 0.0.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +381 -0
  3. package/dist/action/action.js +44 -0
  4. package/dist/action/catalog.js +40 -0
  5. package/dist/action/cooldown.js +60 -0
  6. package/dist/action/executor.js +23 -0
  7. package/dist/action/template.js +13 -0
  8. package/dist/action/types.js +2 -0
  9. package/dist/action/webhook.js +36 -0
  10. package/dist/actions/action.js +26 -0
  11. package/dist/actions/cooldown.js +66 -0
  12. package/dist/actions/request.js +40 -0
  13. package/dist/api/routes/auth.js +56 -0
  14. package/dist/api/routes/bots.js +51 -0
  15. package/dist/api/routes/config.js +24 -0
  16. package/dist/api/routes/health.js +15 -0
  17. package/dist/api/routes/index.js +14 -0
  18. package/dist/api/routes/messages.js +69 -0
  19. package/dist/api/routes/sessions.js +97 -0
  20. package/dist/api/routes/status.js +38 -0
  21. package/dist/api/server.js +153 -0
  22. package/dist/auth/service.js +102 -0
  23. package/dist/boot/fleet.js +207 -0
  24. package/dist/bot/bot.js +30 -0
  25. package/dist/bot/fleet.js +211 -0
  26. package/dist/bot/fuzzy.js +20 -0
  27. package/dist/bot/mapper.js +47 -0
  28. package/dist/bot/types.js +2 -0
  29. package/dist/bot/validation.js +38 -0
  30. package/dist/bot.js +31 -0
  31. package/dist/cli/create-bot.js +84 -0
  32. package/dist/cli.js +138 -0
  33. package/dist/commands/auth.js +200 -0
  34. package/dist/commands/create-bot.js +64 -0
  35. package/dist/commands/guide.js +563 -0
  36. package/dist/commands/lock.js +73 -0
  37. package/dist/commands/status.js +145 -0
  38. package/dist/commands/unlock.js +88 -0
  39. package/dist/commands/validate.js +19 -0
  40. package/dist/config/mapper.js +152 -0
  41. package/dist/config/schema.js +2 -0
  42. package/dist/config/validation.js +705 -0
  43. package/dist/config/watcher.js +145 -0
  44. package/dist/config/yaml.js +197 -0
  45. package/dist/fleet.js +247 -0
  46. package/dist/flow/executor.js +286 -0
  47. package/dist/flow/flow.js +2 -0
  48. package/dist/flow/mapper.js +72 -0
  49. package/dist/flow/state.js +115 -0
  50. package/dist/flow/types.js +2 -0
  51. package/dist/graph/executor.js +294 -0
  52. package/dist/graph/graph.js +2 -0
  53. package/dist/graph/state.js +118 -0
  54. package/dist/helpers/data.js +42 -0
  55. package/dist/helpers/fuzzy.js +30 -0
  56. package/dist/helpers/logger.js +89 -0
  57. package/dist/helpers/validation.js +38 -0
  58. package/dist/index.js +92 -0
  59. package/dist/messages/contracts.js +2 -0
  60. package/dist/messages/inbox.js +58 -0
  61. package/dist/messages/outbox.js +136 -0
  62. package/dist/services/auto-response.js +62 -0
  63. package/dist/services/cooldown.js +60 -0
  64. package/dist/services/fleet.js +207 -0
  65. package/dist/services/flow-executor.js +195 -0
  66. package/dist/services/flow-state.js +118 -0
  67. package/dist/services/inbox.js +50 -0
  68. package/dist/services/message-handler.js +78 -0
  69. package/dist/services/message-queue.js +138 -0
  70. package/dist/services/outbox.js +138 -0
  71. package/dist/services/session.js +118 -0
  72. package/dist/services/webhook.js +87 -0
  73. package/dist/utils/logger.js +89 -0
  74. package/dist/utils/webhook.js +36 -0
  75. package/dist/validation/validate.js +592 -0
  76. package/dist/whatsapp/client.js +293 -0
  77. package/dist/whatsapp/session.js +158 -0
  78. package/dist/whatsapp/types.js +109 -0
  79. package/dist/whatsapp/whatsapp.js +109 -0
  80. package/package.json +97 -0
  81. package/scripts/postinstall.js +45 -0
  82. package/scripts/preuninstall.js +48 -0
  83. package/scripts/setup-systemd.js +91 -0
  84. package/service/botforje.service.template +25 -0
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CooldownService = void 0;
4
+ class CooldownService {
5
+ constructor() {
6
+ this.cooldowns = new Map();
7
+ }
8
+ isOnCooldown(sender, key, cooldownMs) {
9
+ if (cooldownMs <= 0)
10
+ return false;
11
+ const senderCooldowns = this.cooldowns.get(sender);
12
+ if (!senderCooldowns)
13
+ return false;
14
+ const lastTrigger = senderCooldowns.get(key);
15
+ if (!lastTrigger)
16
+ return false;
17
+ return Date.now() - lastTrigger < cooldownMs;
18
+ }
19
+ setCooldown(sender, key) {
20
+ let senderCooldowns = this.cooldowns.get(sender);
21
+ if (!senderCooldowns) {
22
+ senderCooldowns = new Map();
23
+ this.cooldowns.set(sender, senderCooldowns);
24
+ }
25
+ senderCooldowns.set(key, Date.now());
26
+ }
27
+ cleanupExpiredCooldowns() {
28
+ const now = Date.now();
29
+ const maxAge = 60 * 60 * 1000;
30
+ for (const [sender, keyMap] of this.cooldowns.entries()) {
31
+ for (const [key, timestamp] of keyMap.entries()) {
32
+ if (now - timestamp > maxAge) {
33
+ keyMap.delete(key);
34
+ }
35
+ }
36
+ if (keyMap.size === 0) {
37
+ this.cooldowns.delete(sender);
38
+ }
39
+ }
40
+ }
41
+ getCooldownStatus(sender, key) {
42
+ const senderCooldowns = this.cooldowns.get(sender);
43
+ const lastTrigger = senderCooldowns?.get(key);
44
+ if (!lastTrigger) {
45
+ return { isOnCooldown: false, remainingMs: 0 };
46
+ }
47
+ const now = Date.now();
48
+ const elapsed = now - lastTrigger;
49
+ const isOnCooldown = elapsed < 30000;
50
+ const remainingMs = Math.max(0, 30000 - elapsed);
51
+ return { isOnCooldown, remainingMs, lastTrigger };
52
+ }
53
+ clearAllCooldowns() {
54
+ this.cooldowns.clear();
55
+ }
56
+ clearSenderCooldowns(sender) {
57
+ this.cooldowns.delete(sender);
58
+ }
59
+ removeCooldown(sender, key) {
60
+ const senderCooldowns = this.cooldowns.get(sender);
61
+ if (senderCooldowns) {
62
+ senderCooldowns.delete(key);
63
+ }
64
+ }
65
+ }
66
+ exports.CooldownService = CooldownService;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendRequest = sendRequest;
4
+ const logger_1 = require("../helpers/logger");
5
+ async function sendRequest(call) {
6
+ const logger = (0, logger_1.getLogger)();
7
+ const maxRetries = call.retries || 1;
8
+ let lastError = null;
9
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
10
+ const controller = new AbortController();
11
+ const timeoutId = setTimeout(() => controller.abort(), call.timeout);
12
+ try {
13
+ const response = await fetch(call.url, {
14
+ method: call.method,
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ ...call.headers,
18
+ },
19
+ body: call.body !== undefined ? JSON.stringify(call.body) : undefined,
20
+ signal: controller.signal,
21
+ });
22
+ clearTimeout(timeoutId);
23
+ if (!response.ok) {
24
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
25
+ }
26
+ logger.debug(`Request successful: ${call.url}`);
27
+ return;
28
+ }
29
+ catch (error) {
30
+ clearTimeout(timeoutId);
31
+ lastError = error instanceof Error ? error : new Error(String(error));
32
+ if (attempt < maxRetries) {
33
+ const backoffMs = Math.pow(2, attempt - 1) * 1000;
34
+ logger.warn(`Request failed (attempt ${attempt}/${maxRetries}), retrying in ${backoffMs}ms: ${lastError.message}`);
35
+ await new Promise(resolve => setTimeout(resolve, backoffMs));
36
+ }
37
+ }
38
+ }
39
+ throw new Error(`Request failed after ${maxRetries} attempts: ${lastError?.message}`);
40
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAuthRouter = createAuthRouter;
4
+ exports.findSessionToken = findSessionToken;
5
+ const express_1 = require("express");
6
+ function parseCookie(cookie, name) {
7
+ if (!cookie)
8
+ return null;
9
+ for (const part of cookie.split(';')) {
10
+ const trimmed = part.trim();
11
+ const eqIdx = trimmed.indexOf('=');
12
+ if (eqIdx === -1)
13
+ continue;
14
+ if (trimmed.slice(0, eqIdx) === name) {
15
+ return trimmed.slice(eqIdx + 1);
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+ function createAuthRouter(authService) {
21
+ const router = (0, express_1.Router)();
22
+ router.post('/login', (req, res) => {
23
+ const { key } = req.body;
24
+ if (!key || typeof key !== 'string') {
25
+ res.status(400).json({ error: 'key is required' });
26
+ return;
27
+ }
28
+ if (!authService.isLocked()) {
29
+ res.status(400).json({ error: 'Auth is not enabled. Run `botforje lock` first.' });
30
+ return;
31
+ }
32
+ if (!authService.verifyKey(key)) {
33
+ res.status(401).json({ error: 'Invalid key' });
34
+ return;
35
+ }
36
+ const session = authService.createSession();
37
+ res.setHeader('Set-Cookie', `botforje_session=${session.token}; HttpOnly; SameSite=Strict; Path=/; Max-Age=86400`);
38
+ res.json({ success: true, expiresAt: session.expiresAt });
39
+ });
40
+ router.post('/logout', (req, res) => {
41
+ const token = parseCookie(req.headers.cookie, 'botforje_session');
42
+ if (token) {
43
+ authService.invalidateSession(token);
44
+ }
45
+ res.setHeader('Set-Cookie', 'botforje_session=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0');
46
+ res.json({ success: true });
47
+ });
48
+ return router;
49
+ }
50
+ function findSessionToken(authService, cookieHeader) {
51
+ const token = parseCookie(cookieHeader, 'botforje_session');
52
+ if (!token)
53
+ return null;
54
+ const session = authService.validateSession(token);
55
+ return session ? token : null;
56
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBotsRouter = createBotsRouter;
4
+ const express_1 = require("express");
5
+ function createBotsRouter(bots) {
6
+ const router = (0, express_1.Router)();
7
+ router.get('/', (req, res) => {
8
+ try {
9
+ const botList = Array.from(bots.values()).map(bot => ({
10
+ id: bot.id,
11
+ phone: bot.phone,
12
+ graph: bot.graph,
13
+ settings: bot.settings,
14
+ }));
15
+ res.json({
16
+ bots: botList,
17
+ total: botList.length,
18
+ });
19
+ }
20
+ catch (error) {
21
+ console.error('Error getting bots:', error);
22
+ res.status(500).json({
23
+ error: 'Internal server error',
24
+ });
25
+ }
26
+ });
27
+ router.get('/:botId', (req, res) => {
28
+ try {
29
+ const { botId } = req.params;
30
+ if (!bots.has(botId)) {
31
+ return res.status(404).json({
32
+ error: `Bot with id '${botId}' not found`,
33
+ });
34
+ }
35
+ const bot = bots.get(botId);
36
+ res.json({
37
+ id: bot.id,
38
+ phone: bot.phone,
39
+ graph: bot.graph,
40
+ settings: bot.settings,
41
+ });
42
+ }
43
+ catch (error) {
44
+ console.error('Error getting bot:', error);
45
+ res.status(500).json({
46
+ error: 'Internal server error',
47
+ });
48
+ }
49
+ });
50
+ return router;
51
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createConfigRouter = createConfigRouter;
4
+ const express_1 = require("express");
5
+ const logger_1 = require("../../helpers/logger");
6
+ function createConfigRouter(fleet, configWatcher) {
7
+ const router = (0, express_1.Router)();
8
+ router.post('/reload', async (req, res) => {
9
+ try {
10
+ await configWatcher.reload();
11
+ res.json({ success: true, message: 'Configuration reloaded' });
12
+ }
13
+ catch (error) {
14
+ (0, logger_1.getLogger)().error('Config reload error:', error);
15
+ res.status(500).json({ error: error.message || 'Reload failed' });
16
+ }
17
+ });
18
+ router.get('/status', (req, res) => {
19
+ res.json({
20
+ watching: configWatcher.isWatching(),
21
+ });
22
+ });
23
+ return router;
24
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createHealthRouter = createHealthRouter;
4
+ const express_1 = require("express");
5
+ function createHealthRouter() {
6
+ const router = (0, express_1.Router)();
7
+ router.get('/', (req, res) => {
8
+ res.json({
9
+ status: 'ok',
10
+ timestamp: new Date().toISOString(),
11
+ service: 'Botforje API',
12
+ });
13
+ });
14
+ return router;
15
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createApiRoutes = createApiRoutes;
4
+ const express_1 = require("express");
5
+ const health_1 = require("./health");
6
+ const messages_1 = require("./messages");
7
+ const bots_1 = require("./bots");
8
+ function createApiRoutes(outboxService, bots) {
9
+ const router = (0, express_1.Router)();
10
+ router.use('/health', (0, health_1.createHealthRouter)());
11
+ router.use('/messages', (0, messages_1.createMessagesRouter)(outboxService, bots));
12
+ router.use('/bots', (0, bots_1.createBotsRouter)(bots));
13
+ return router;
14
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMessagesRouter = createMessagesRouter;
4
+ const express_1 = require("express");
5
+ function createMessagesRouter(outboxService, bots) {
6
+ const router = (0, express_1.Router)();
7
+ router.post('/send', (req, res) => {
8
+ try {
9
+ const { botId, to, content, metadata } = req.body;
10
+ if (!botId || !to || !content) {
11
+ return res.status(400).json({
12
+ error: 'Missing required fields: botId, to, content',
13
+ });
14
+ }
15
+ if (!bots.has(botId)) {
16
+ return res.status(404).json({
17
+ error: `Bot with id '${botId}' not found`,
18
+ });
19
+ }
20
+ const messageId = outboxService.enqueue(botId, to, content, metadata);
21
+ res.json({
22
+ success: true,
23
+ messageId,
24
+ botId,
25
+ queued: true,
26
+ });
27
+ }
28
+ catch (error) {
29
+ console.error('Error sending message:', error);
30
+ res.status(500).json({
31
+ error: 'Internal server error',
32
+ });
33
+ }
34
+ });
35
+ router.get('/queue/:botId', (req, res) => {
36
+ try {
37
+ const { botId } = req.params;
38
+ if (!bots.has(botId)) {
39
+ return res.status(404).json({
40
+ error: `Bot with id '${botId}' not found`,
41
+ });
42
+ }
43
+ const queueStatus = outboxService.getBotQueueStatus(botId);
44
+ res.json({
45
+ botId,
46
+ queue: queueStatus,
47
+ });
48
+ }
49
+ catch (error) {
50
+ console.error('Error getting queue status:', error);
51
+ res.status(500).json({
52
+ error: 'Internal server error',
53
+ });
54
+ }
55
+ });
56
+ router.get('/queue', (req, res) => {
57
+ try {
58
+ const queueStatus = outboxService.getAllQueuesStatus();
59
+ res.json(queueStatus);
60
+ }
61
+ catch (error) {
62
+ console.error('Error getting all queues status:', error);
63
+ res.status(500).json({
64
+ error: 'Internal server error',
65
+ });
66
+ }
67
+ });
68
+ return router;
69
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSessionsRouter = createSessionsRouter;
4
+ const express_1 = require("express");
5
+ const session_1 = require("../../whatsapp/session");
6
+ const logger_1 = require("../../helpers/logger");
7
+ function createSessionsRouter() {
8
+ const router = (0, express_1.Router)();
9
+ const sessionManager = session_1.SessionManager.getInstance();
10
+ const logger = (0, logger_1.getLogger)();
11
+ router.post('/:id', async (req, res) => {
12
+ try {
13
+ const { id } = req.params;
14
+ const existingInfo = sessionManager.getSessionInfo(id);
15
+ if (existingInfo?.state === 'connected') {
16
+ return res.status(409).json({
17
+ error: `Session "${id}" is already authenticated`,
18
+ session: existingInfo,
19
+ });
20
+ }
21
+ if (existingInfo && (existingInfo.state === 'qr_received' || existingInfo.state === 'pending')) {
22
+ return res.json({
23
+ success: true,
24
+ id,
25
+ session: existingInfo,
26
+ note: 'Session already pending, subscribe to events',
27
+ });
28
+ }
29
+ await sessionManager.registerSession(id);
30
+ const info = sessionManager.getSessionInfo(id);
31
+ res.json({
32
+ success: true,
33
+ id,
34
+ session: info,
35
+ });
36
+ }
37
+ catch (error) {
38
+ logger.error(`Error registering session: ${error.message || String(error)}`);
39
+ res.status(500).json({ error: error.message || 'Internal server error' });
40
+ }
41
+ });
42
+ router.get('/:id', (req, res) => {
43
+ const { id } = req.params;
44
+ const info = sessionManager.getSessionInfo(id);
45
+ if (!info) {
46
+ return res.status(404).json({ error: `Session "${id}" not found` });
47
+ }
48
+ res.json({ id, session: info });
49
+ });
50
+ router.delete('/:id', async (req, res) => {
51
+ try {
52
+ const { id } = req.params;
53
+ if (!sessionManager.hasChannel(id)) {
54
+ return res.status(404).json({ error: `Session "${id}" not found` });
55
+ }
56
+ await sessionManager.removeChannel(id);
57
+ res.json({ success: true, id });
58
+ }
59
+ catch (error) {
60
+ logger.error(`Error removing session: ${error.message || String(error)}`);
61
+ res.status(500).json({ error: error.message || 'Internal server error' });
62
+ }
63
+ });
64
+ router.get('/:id/events', (req, res) => {
65
+ const { id } = req.params;
66
+ if (!sessionManager.hasChannel(id)) {
67
+ return res.status(404).json({ error: `Session "${id}" not found` });
68
+ }
69
+ res.writeHead(200, {
70
+ 'Content-Type': 'text/event-stream',
71
+ 'Cache-Control': 'no-cache',
72
+ 'Connection': 'keep-alive',
73
+ 'X-Accel-Buffering': 'no',
74
+ });
75
+ const handler = (event) => {
76
+ try {
77
+ res.write(`event: ${event.type}\n`);
78
+ res.write(`data: ${JSON.stringify(event.data ?? {})}\n\n`);
79
+ }
80
+ catch {
81
+ // connection closed
82
+ }
83
+ };
84
+ sessionManager.onSessionEvent(id, handler);
85
+ req.on('close', () => {
86
+ sessionManager.removeSessionEventListener(id, handler);
87
+ });
88
+ });
89
+ router.get('/', (req, res) => {
90
+ const sessions = Array.from(sessionManager.getAllSessions().entries()).map(([id, info]) => ({
91
+ id,
92
+ ...info,
93
+ }));
94
+ res.json({ sessions, total: sessions.length });
95
+ });
96
+ return router;
97
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStatusRouter = createStatusRouter;
4
+ const express_1 = require("express");
5
+ const session_1 = require("../../whatsapp/session");
6
+ const logger_1 = require("../../helpers/logger");
7
+ function createStatusRouter(bots) {
8
+ const router = (0, express_1.Router)();
9
+ const sessionManager = session_1.SessionManager.getInstance();
10
+ router.get('/', (req, res) => {
11
+ try {
12
+ const logger = (0, logger_1.getLogger)();
13
+ const botStatuses = Array.from(bots.entries()).map(([id, bot]) => {
14
+ const sessionInfo = sessionManager.getSessionInfo(id);
15
+ return {
16
+ id,
17
+ graph: bot.graph,
18
+ phone: sessionInfo?.phone || bot.phone || null,
19
+ session: sessionInfo ? {
20
+ state: sessionInfo.state,
21
+ lastQR: sessionInfo.lastQR ? true : false,
22
+ error: sessionInfo.error || null,
23
+ } : null,
24
+ };
25
+ });
26
+ res.json({
27
+ running: true,
28
+ bots: botStatuses,
29
+ total: botStatuses.length,
30
+ });
31
+ }
32
+ catch (error) {
33
+ (0, logger_1.getLogger)().error('Error getting status:', error);
34
+ res.status(500).json({ error: 'Internal server error' });
35
+ }
36
+ });
37
+ return router;
38
+ }
@@ -0,0 +1,153 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ApiServer = void 0;
37
+ const express_1 = __importStar(require("express"));
38
+ const health_1 = require("./routes/health");
39
+ const bots_1 = require("./routes/bots");
40
+ const messages_1 = require("./routes/messages");
41
+ const sessions_1 = require("./routes/sessions");
42
+ const status_1 = require("./routes/status");
43
+ const config_1 = require("./routes/config");
44
+ const auth_1 = require("./routes/auth");
45
+ const logger_1 = require("../helpers/logger");
46
+ class ApiServer {
47
+ constructor(outboxService, bots, authService, port = 3000, fleet, configWatcher, address = '127.0.0.1') {
48
+ this.app = (0, express_1.default)();
49
+ this.port = port;
50
+ this.address = address;
51
+ this.authService = authService;
52
+ this.outboxService = outboxService;
53
+ this.bots = bots;
54
+ this.fleet = fleet;
55
+ this.configWatcher = configWatcher;
56
+ this.setupMiddleware();
57
+ this.setupAuth();
58
+ this.setupRoutes();
59
+ }
60
+ get logger() {
61
+ return (0, logger_1.getLogger)();
62
+ }
63
+ setupMiddleware() {
64
+ this.app.use(express_1.default.json());
65
+ this.app.use((req, res, next) => {
66
+ res.header('Access-Control-Allow-Origin', '*');
67
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
68
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
69
+ if (req.method === 'OPTIONS') {
70
+ res.sendStatus(200);
71
+ }
72
+ else {
73
+ next();
74
+ }
75
+ });
76
+ }
77
+ setupAuth() {
78
+ this.app.use('/api', (req, res, next) => {
79
+ if (req.path === '/health')
80
+ return next();
81
+ if (!this.authService.isLocked())
82
+ return next();
83
+ const sessionToken = (0, auth_1.findSessionToken)(this.authService, req.headers.cookie);
84
+ if (sessionToken)
85
+ return next();
86
+ const auth = req.headers.authorization;
87
+ if (auth && auth.startsWith('Bearer ')) {
88
+ const key = auth.slice(7);
89
+ if (this.authService.verifyKey(key))
90
+ return next();
91
+ }
92
+ res.status(401).json({ error: 'Unauthorized' });
93
+ });
94
+ }
95
+ setupRoutes() {
96
+ const api = (0, express_1.Router)();
97
+ api.use('/health', (0, health_1.createHealthRouter)());
98
+ api.use('/auth', (0, auth_1.createAuthRouter)(this.authService));
99
+ api.use('/bots', (0, bots_1.createBotsRouter)(this.bots));
100
+ api.use('/messages', (0, messages_1.createMessagesRouter)(this.outboxService, this.bots));
101
+ api.use('/sessions', (0, sessions_1.createSessionsRouter)());
102
+ api.use('/status', (0, status_1.createStatusRouter)(this.bots));
103
+ if (this.fleet && this.configWatcher) {
104
+ api.use('/config', (0, config_1.createConfigRouter)(this.fleet, this.configWatcher));
105
+ }
106
+ this.app.use('/api', api);
107
+ this.app.get('/', (req, res) => {
108
+ const endpoints = {
109
+ health: '/api/health',
110
+ auth: '/api/auth',
111
+ messages: '/api/messages',
112
+ bots: '/api/bots',
113
+ sessions: '/api/sessions',
114
+ status: '/api/status',
115
+ };
116
+ if (this.fleet && this.configWatcher) {
117
+ endpoints.config = '/api/config';
118
+ }
119
+ res.json({
120
+ service: 'Botforje API',
121
+ endpoints,
122
+ });
123
+ });
124
+ this.app.use((req, res) => {
125
+ res.status(404).json({
126
+ error: 'Endpoint not found',
127
+ });
128
+ });
129
+ }
130
+ async start() {
131
+ return new Promise((resolve) => {
132
+ this.server = this.app.listen(this.port, this.address, () => {
133
+ this.logger.info(`API Server started on ${this.address}:${this.port}`);
134
+ resolve();
135
+ });
136
+ });
137
+ }
138
+ async stop() {
139
+ return new Promise((resolve) => {
140
+ if (this.server) {
141
+ this.server.close(() => {
142
+ this.logger.info('API Server stopped');
143
+ resolve();
144
+ });
145
+ }
146
+ else {
147
+ this.logger.info('API Server stopped');
148
+ resolve();
149
+ }
150
+ });
151
+ }
152
+ }
153
+ exports.ApiServer = ApiServer;