@pioneer-platform/discord 1.0.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.
@@ -0,0 +1,2 @@
1
+
2
+ $ tsc
package/README.md ADDED
@@ -0,0 +1,231 @@
1
+ # @pioneer-platform/discord
2
+
3
+ Discord integration for Pioneer Platform - Redis to Discord event bridge.
4
+
5
+ ## Overview
6
+
7
+ This module subscribes to Redis pub/sub channels and forwards events to Discord channels. It's designed to monitor server health, errors, and important events in real-time.
8
+
9
+ ## Features
10
+
11
+ - šŸ”„ **Redis → Discord Bridge**: Subscribe to Redis channels, forward to Discord
12
+ - šŸŽØ **Rich Embeds**: Color-coded messages with severity levels
13
+ - šŸ“Š **Event Formatting**: Structured event display with fields
14
+ - ⚔ **Real-time**: Instant notification delivery
15
+ - šŸ›”ļø **Error Handling**: Robust error recovery
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ bun install
21
+ bun run build
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ Required environment variables:
27
+
28
+ ```bash
29
+ # Discord Bot Configuration
30
+ KEEPKEY_DISCORD_BOT_TOKEN=<your-bot-token>
31
+ KEEPKEY_BOT_NAME=pioneer
32
+ KEEPKEY_DISCORD_BOT_CHANNEL=keepkey-events
33
+
34
+ # Optional
35
+ DISCORD_ADMIN_USERID=<discord-user-id>
36
+
37
+ # Redis Connection
38
+ REDIS_CONNECTION=redis://localhost:6379
39
+ # or
40
+ REDIS_CONNECTION_PROD=rediss://...
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### Basic Usage
46
+
47
+ ```typescript
48
+ import { createDiscordBridge } from '@pioneer-platform/discord';
49
+ import Redis from 'ioredis';
50
+
51
+ const redis = new Redis(process.env.REDIS_CONNECTION);
52
+
53
+ const bridge = await createDiscordBridge({
54
+ botToken: process.env.KEEPKEY_DISCORD_BOT_TOKEN!,
55
+ botName: 'pioneer',
56
+ defaultChannel: 'keepkey-events',
57
+ redisChannels: ['keepkey-events']
58
+ }, redis);
59
+
60
+ // Bridge is now listening and will forward events
61
+ ```
62
+
63
+ ### Publishing Events
64
+
65
+ ```typescript
66
+ import { publisher } from '@pioneer-platform/default-redis';
67
+
68
+ // Publish an event
69
+ await publisher.publish('keepkey-events', JSON.stringify({
70
+ type: 'server_startup',
71
+ service: 'pioneer-server',
72
+ level: 'success',
73
+ message: 'Server started successfully',
74
+ timestamp: new Date().toISOString(),
75
+ details: {
76
+ port: '9001',
77
+ environment: 'production'
78
+ }
79
+ }));
80
+ ```
81
+
82
+ ## Event Format
83
+
84
+ Events should follow this structure:
85
+
86
+ ```typescript
87
+ interface DiscordEvent {
88
+ type: string; // Event type (e.g., 'server_startup', 'error')
89
+ service?: string; // Service name (e.g., 'pioneer-server')
90
+ level?: 'info' | 'success' | 'warning' | 'error' | 'critical';
91
+ message: string; // Main message
92
+ timestamp?: string; // ISO timestamp
93
+ details?: Record<string, any>; // Additional fields
94
+ }
95
+ ```
96
+
97
+ ### Severity Levels
98
+
99
+ | Level | Color | Icon | Use Case |
100
+ |-------|-------|------|----------|
101
+ | `info` | Blue | ā„¹ļø | General information |
102
+ | `success` | Green | āœ… | Successful operations |
103
+ | `warning` | Orange | āš ļø | Non-critical issues |
104
+ | `error` | Red | āŒ | Errors |
105
+ | `critical` | Dark Red | 🚨 | Critical failures |
106
+
107
+ ## Testing
108
+
109
+ ### Standalone Test
110
+
111
+ ```bash
112
+ # Set environment variables in .env file
113
+ KEEPKEY_DISCORD_BOT_TOKEN=...
114
+ REDIS_CONNECTION_PROD=...
115
+
116
+ # Run the test
117
+ bun run src/test.ts
118
+ ```
119
+
120
+ This will:
121
+ 1. Connect to Discord
122
+ 2. Subscribe to `keepkey-events` Redis channel
123
+ 3. Publish 5 test events with different severity levels
124
+ 4. Verify they appear in Discord
125
+
126
+ ### Example Events
127
+
128
+ **Server Startup:**
129
+ ```json
130
+ {
131
+ "type": "server_startup",
132
+ "service": "pioneer-server",
133
+ "level": "success",
134
+ "message": "Pioneer Server started successfully",
135
+ "details": {
136
+ "port": "9001",
137
+ "environment": "production"
138
+ }
139
+ }
140
+ ```
141
+
142
+ **Error:**
143
+ ```json
144
+ {
145
+ "type": "database_error",
146
+ "service": "pioneer-server",
147
+ "level": "error",
148
+ "message": "Failed to connect to MongoDB",
149
+ "details": {
150
+ "error": "Connection timeout",
151
+ "retries": 3
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## Discord Bot Setup
157
+
158
+ 1. **Create Discord Application**:
159
+ - Go to https://discord.com/developers/applications
160
+ - Click "New Application"
161
+ - Name it "Pioneer Bot"
162
+
163
+ 2. **Create Bot**:
164
+ - Go to "Bot" tab
165
+ - Click "Add Bot"
166
+ - Copy the token → `KEEPKEY_DISCORD_BOT_TOKEN`
167
+
168
+ 3. **Bot Permissions**:
169
+ - Enable "Message Content Intent"
170
+ - Required permissions:
171
+ - Send Messages
172
+ - Embed Links
173
+ - Read Message History
174
+
175
+ 4. **Invite Bot to Server**:
176
+ - Go to "OAuth2" → "URL Generator"
177
+ - Select scopes: `bot`
178
+ - Select permissions: `2048` (Send Messages), `16384` (Embed Links)
179
+ - Use generated URL to invite bot
180
+
181
+ 5. **Create Channel**:
182
+ - Create `#keepkey-events` channel in your Discord server
183
+ - Bot will automatically find it by name
184
+
185
+ ## Integration with Pioneer Server
186
+
187
+ See `../../services/pioneer-server/docs/DISCORD_INTEGRATION.md` for server integration details.
188
+
189
+ ## API Reference
190
+
191
+ ### DiscordBridge
192
+
193
+ ```typescript
194
+ class DiscordBridge {
195
+ constructor(config: DiscordConfig, redis: Redis)
196
+ async start(): Promise<void>
197
+ async stop(): Promise<void>
198
+ isConnected(): boolean
199
+ getClient(): Client
200
+ }
201
+ ```
202
+
203
+ ### createDiscordBridge
204
+
205
+ ```typescript
206
+ function createDiscordBridge(
207
+ config: DiscordConfig,
208
+ redis: Redis
209
+ ): Promise<DiscordBridge>
210
+ ```
211
+
212
+ ## Troubleshooting
213
+
214
+ ### Bot doesn't connect
215
+ - Verify `KEEPKEY_DISCORD_BOT_TOKEN` is correct
216
+ - Check bot has "Message Content Intent" enabled
217
+ - Ensure bot is invited to server
218
+
219
+ ### Messages don't appear in Discord
220
+ - Verify channel name matches `KEEPKEY_DISCORD_BOT_CHANNEL`
221
+ - Check bot has permissions in the channel
222
+ - Look for errors in logs
223
+
224
+ ### Redis connection fails
225
+ - Verify `REDIS_CONNECTION_PROD` is correct
226
+ - Check Redis server is accessible
227
+ - Confirm Redis channels match configuration
228
+
229
+ ## License
230
+
231
+ MIT
package/lib/index.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Discord Integration for Pioneer Platform
3
+ *
4
+ * Subscribes to Redis channels and forwards events to Discord channels
5
+ */
6
+ import { Client } from 'discord.js';
7
+ import type { Redis } from 'ioredis';
8
+ export interface DiscordConfig {
9
+ botToken: string;
10
+ botName?: string;
11
+ defaultChannel: string;
12
+ adminUserId?: string;
13
+ redisChannels: string[];
14
+ }
15
+ export interface DiscordEvent {
16
+ type: string;
17
+ service?: string;
18
+ level?: 'info' | 'success' | 'warning' | 'error' | 'critical';
19
+ message: string;
20
+ timestamp?: string;
21
+ details?: Record<string, any>;
22
+ [key: string]: any;
23
+ }
24
+ export declare class DiscordBridge {
25
+ private client;
26
+ private config;
27
+ private redis;
28
+ private isReady;
29
+ private targetChannel?;
30
+ private environment;
31
+ constructor(config: DiscordConfig, redis: Redis);
32
+ /**
33
+ * Setup Discord client event handlers
34
+ */
35
+ private setupEventHandlers;
36
+ /**
37
+ * Find the Discord channel to post to
38
+ */
39
+ private findTargetChannel;
40
+ /**
41
+ * Subscribe to Redis channels
42
+ */
43
+ private subscribeToRedis;
44
+ /**
45
+ * Handle incoming Redis messages
46
+ */
47
+ private handleRedisMessage;
48
+ /**
49
+ * Post event to Discord channel
50
+ */
51
+ private postToDiscord;
52
+ /**
53
+ * Start the Discord bridge
54
+ */
55
+ start(): Promise<void>;
56
+ /**
57
+ * Stop the Discord bridge
58
+ */
59
+ stop(): Promise<void>;
60
+ /**
61
+ * Check if the bridge is ready
62
+ */
63
+ isConnected(): boolean;
64
+ /**
65
+ * Get the Discord client (for advanced usage)
66
+ */
67
+ getClient(): Client;
68
+ }
69
+ /**
70
+ * Create and start a Discord bridge
71
+ */
72
+ export declare function createDiscordBridge(config: DiscordConfig, redis: Redis): Promise<DiscordBridge>;
73
+ export default DiscordBridge;
74
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAgD,MAAM,YAAY,CAAC;AAClF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAKrC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE9B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAkBD,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,aAAa,CAAC,CAAc;IACpC,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK;IAiB/C;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAqB1B;;OAEG;YACW,iBAAiB;IAyB/B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsBxB;;OAEG;YACW,kBAAkB;IAuBhC;;OAEG;YACW,aAAa;IAmD3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB3B;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,SAAS,IAAI,MAAM;CAGpB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,aAAa,CAAC,CAIxB;AAED,eAAe,aAAa,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,233 @@
1
+ "use strict";
2
+ /**
3
+ * Discord Integration for Pioneer Platform
4
+ *
5
+ * Subscribes to Redis channels and forwards events to Discord channels
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.DiscordBridge = void 0;
9
+ exports.createDiscordBridge = createDiscordBridge;
10
+ const discord_js_1 = require("discord.js");
11
+ const log = require('@pioneer-platform/loggerdog')();
12
+ const TAG = ' | Discord Integration | ';
13
+ const LEVEL_COLORS = {
14
+ info: 0x3498db, // Blue
15
+ success: 0x2ecc71, // Green
16
+ warning: 0xf39c12, // Orange
17
+ error: 0xe74c3c, // Red
18
+ critical: 0x992d22 // Dark Red
19
+ };
20
+ const LEVEL_ICONS = {
21
+ info: 'ā„¹ļø',
22
+ success: 'āœ…',
23
+ warning: 'āš ļø',
24
+ error: 'āŒ',
25
+ critical: '🚨'
26
+ };
27
+ class DiscordBridge {
28
+ constructor(config, redis) {
29
+ this.isReady = false;
30
+ this.config = config;
31
+ this.redis = redis;
32
+ this.environment = process.env.LOCAL_ENV || 'production';
33
+ // Create Discord client with necessary intents
34
+ this.client = new discord_js_1.Client({
35
+ intents: [
36
+ discord_js_1.GatewayIntentBits.Guilds,
37
+ discord_js_1.GatewayIntentBits.GuildMessages,
38
+ discord_js_1.GatewayIntentBits.MessageContent
39
+ ]
40
+ });
41
+ this.setupEventHandlers();
42
+ }
43
+ /**
44
+ * Setup Discord client event handlers
45
+ */
46
+ setupEventHandlers() {
47
+ this.client.on('ready', async () => {
48
+ log.info(TAG, `[${this.environment.toUpperCase()}] āœ… Discord bot logged in as ${this.client.user?.tag}`);
49
+ this.isReady = true;
50
+ // Find the target channel
51
+ await this.findTargetChannel();
52
+ // Subscribe to Redis channels
53
+ this.subscribeToRedis();
54
+ });
55
+ this.client.on('error', (error) => {
56
+ log.error(TAG, 'Discord client error:', error);
57
+ });
58
+ this.client.on('warn', (warning) => {
59
+ log.warn(TAG, 'Discord client warning:', warning);
60
+ });
61
+ }
62
+ /**
63
+ * Find the Discord channel to post to
64
+ */
65
+ async findTargetChannel() {
66
+ const tag = TAG + ' | findTargetChannel | ';
67
+ try {
68
+ // Get all channels
69
+ const channels = this.client.channels.cache;
70
+ log.info(tag, `Searching ${channels.size} cached channels for: ${this.config.defaultChannel}`);
71
+ // Find by name
72
+ const channel = channels.find((ch) => ch.name === this.config.defaultChannel && ch.isTextBased());
73
+ if (channel) {
74
+ this.targetChannel = channel;
75
+ log.info(tag, `āœ… Found target channel: #${channel.name} (${channel.id})`);
76
+ }
77
+ else {
78
+ log.error(tag, `āŒ Could not find channel: #${this.config.defaultChannel}`);
79
+ log.info(tag, 'Available channels:', channels.map((ch) => ch.name).join(', '));
80
+ }
81
+ }
82
+ catch (error) {
83
+ log.error(tag, 'Error finding target channel:', error);
84
+ }
85
+ }
86
+ /**
87
+ * Subscribe to Redis channels
88
+ */
89
+ subscribeToRedis() {
90
+ const tag = TAG + ' | subscribeToRedis | ';
91
+ log.info(tag, `Subscribing to Redis channels: ${this.config.redisChannels.join(', ')}`);
92
+ // Subscribe to all configured channels
93
+ this.config.redisChannels.forEach(channel => {
94
+ this.redis.subscribe(channel, (err) => {
95
+ if (err) {
96
+ log.error(tag, `Failed to subscribe to ${channel}:`, err);
97
+ }
98
+ else {
99
+ log.info(tag, `āœ… Subscribed to Redis channel: ${channel}`);
100
+ }
101
+ });
102
+ });
103
+ // Handle incoming messages
104
+ this.redis.on('message', async (channel, message) => {
105
+ await this.handleRedisMessage(channel, message);
106
+ });
107
+ }
108
+ /**
109
+ * Handle incoming Redis messages
110
+ */
111
+ async handleRedisMessage(channel, message) {
112
+ const tag = TAG + ' | handleRedisMessage | ';
113
+ try {
114
+ log.debug(tag, `Received message from ${channel}`);
115
+ // Parse the message
116
+ let event;
117
+ try {
118
+ event = JSON.parse(message);
119
+ }
120
+ catch (parseError) {
121
+ log.error(tag, 'Failed to parse Redis message:', parseError);
122
+ return;
123
+ }
124
+ // Post to Discord
125
+ await this.postToDiscord(event);
126
+ }
127
+ catch (error) {
128
+ log.error(tag, 'Error handling Redis message:', error);
129
+ }
130
+ }
131
+ /**
132
+ * Post event to Discord channel
133
+ */
134
+ async postToDiscord(event) {
135
+ const tag = TAG + ' | postToDiscord | ';
136
+ if (!this.isReady) {
137
+ log.warn(tag, 'Discord client not ready yet');
138
+ return;
139
+ }
140
+ if (!this.targetChannel) {
141
+ log.error(tag, 'No target channel found');
142
+ return;
143
+ }
144
+ try {
145
+ // Determine level
146
+ const level = event.level || 'info';
147
+ const icon = LEVEL_ICONS[level] || 'ā„¹ļø';
148
+ const color = LEVEL_COLORS[level] || LEVEL_COLORS.info;
149
+ // Create embed
150
+ const embed = new discord_js_1.EmbedBuilder()
151
+ .setColor(color)
152
+ .setTitle(`${icon} ${event.type || 'Event'}`)
153
+ .setDescription(event.message)
154
+ .setTimestamp(event.timestamp ? new Date(event.timestamp) : new Date());
155
+ // Add service field if present
156
+ if (event.service) {
157
+ embed.addFields({ name: 'Service', value: event.service, inline: true });
158
+ }
159
+ // Add details fields if present
160
+ if (event.details) {
161
+ Object.entries(event.details).forEach(([key, value]) => {
162
+ embed.addFields({
163
+ name: key,
164
+ value: String(value),
165
+ inline: true
166
+ });
167
+ });
168
+ }
169
+ // Send to Discord
170
+ await this.targetChannel.send({ embeds: [embed] });
171
+ log.info(tag, `āœ… Posted to #${this.targetChannel.name}: ${event.type}`);
172
+ }
173
+ catch (error) {
174
+ log.error(tag, 'Failed to post to Discord:', error);
175
+ }
176
+ }
177
+ /**
178
+ * Start the Discord bridge
179
+ */
180
+ async start() {
181
+ const tag = TAG + ' | start | ';
182
+ log.info(tag, `[${this.environment.toUpperCase()}] Starting Discord bridge...`);
183
+ log.info(tag, `[${this.environment.toUpperCase()}] Bot name: ${this.config.botName || 'N/A'}`);
184
+ log.info(tag, `[${this.environment.toUpperCase()}] Target channel: #${this.config.defaultChannel}`);
185
+ log.info(tag, `[${this.environment.toUpperCase()}] Redis channels: ${this.config.redisChannels.join(', ')}`);
186
+ try {
187
+ await this.client.login(this.config.botToken);
188
+ log.info(tag, 'āœ… Discord bridge started successfully');
189
+ }
190
+ catch (error) {
191
+ log.error(tag, 'āŒ Failed to start Discord bridge:', error);
192
+ throw error;
193
+ }
194
+ }
195
+ /**
196
+ * Stop the Discord bridge
197
+ */
198
+ async stop() {
199
+ const tag = TAG + ' | stop | ';
200
+ log.info(tag, 'Stopping Discord bridge...');
201
+ // Unsubscribe from Redis channels
202
+ this.config.redisChannels.forEach(channel => {
203
+ this.redis.unsubscribe(channel);
204
+ });
205
+ // Destroy Discord client
206
+ this.client.destroy();
207
+ this.isReady = false;
208
+ log.info(tag, 'āœ… Discord bridge stopped');
209
+ }
210
+ /**
211
+ * Check if the bridge is ready
212
+ */
213
+ isConnected() {
214
+ return this.isReady && !!this.targetChannel;
215
+ }
216
+ /**
217
+ * Get the Discord client (for advanced usage)
218
+ */
219
+ getClient() {
220
+ return this.client;
221
+ }
222
+ }
223
+ exports.DiscordBridge = DiscordBridge;
224
+ /**
225
+ * Create and start a Discord bridge
226
+ */
227
+ async function createDiscordBridge(config, redis) {
228
+ const bridge = new DiscordBridge(config, redis);
229
+ await bridge.start();
230
+ return bridge;
231
+ }
232
+ exports.default = DiscordBridge;
233
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/lib/test.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Standalone test for Discord integration
4
+ *
5
+ * Tests the Discord bridge by:
6
+ * 1. Connecting to Discord
7
+ * 2. Subscribing to keepkey-events Redis channel
8
+ * 3. Publishing test events
9
+ * 4. Verifying they appear in Discord
10
+ *
11
+ * Usage:
12
+ * REDIS_CONNECTION_PROD=redis://... bun run src/test.ts
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;GAWG"}
package/lib/test.js ADDED
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env bun
2
+ "use strict";
3
+ /**
4
+ * Standalone test for Discord integration
5
+ *
6
+ * Tests the Discord bridge by:
7
+ * 1. Connecting to Discord
8
+ * 2. Subscribing to keepkey-events Redis channel
9
+ * 3. Publishing test events
10
+ * 4. Verifying they appear in Discord
11
+ *
12
+ * Usage:
13
+ * REDIS_CONNECTION_PROD=redis://... bun run src/test.ts
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __importDefault = (this && this.__importDefault) || function (mod) {
49
+ return (mod && mod.__esModule) ? mod : { "default": mod };
50
+ };
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ const dotenv = __importStar(require("dotenv"));
53
+ const path = __importStar(require("path"));
54
+ const ioredis_1 = __importDefault(require("ioredis"));
55
+ const index_1 = require("./index");
56
+ // Load environment variables
57
+ dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
58
+ const log = require('@pioneer-platform/loggerdog')();
59
+ const TAG = ' | Discord Test | ';
60
+ async function runTest() {
61
+ log.info(TAG, '🧪 Testing Discord Integration\n');
62
+ // Get config from environment
63
+ const botToken = process.env.KEEPKEY_DISCORD_BOT_TOKEN;
64
+ const botName = process.env.KEEPKEY_BOT_NAME || 'pioneer';
65
+ const channelName = process.env.KEEPKEY_DISCORD_BOT_CHANNEL || 'keepkey-events';
66
+ const redisUrl = process.env.REDIS_CONNECTION_PROD || process.env.REDIS_CONNECTION || 'redis://127.0.0.1:6379';
67
+ if (!botToken) {
68
+ console.error('āŒ KEEPKEY_DISCORD_BOT_TOKEN not set in environment');
69
+ process.exit(1);
70
+ }
71
+ log.info(TAG, 'Configuration:');
72
+ log.info(TAG, ` Bot Name: ${botName}`);
73
+ log.info(TAG, ` Channel: #${channelName}`);
74
+ log.info(TAG, ` Redis: ${redisUrl.includes('@') ? redisUrl.split('@')[1] : redisUrl}`);
75
+ log.info(TAG, '');
76
+ // Create Redis clients
77
+ const subscriber = new ioredis_1.default(redisUrl);
78
+ const publisher = new ioredis_1.default(redisUrl);
79
+ // Discord bridge config
80
+ const config = {
81
+ botToken,
82
+ botName,
83
+ defaultChannel: channelName,
84
+ adminUserId: process.env.DISCORD_ADMIN_USERID,
85
+ redisChannels: ['keepkey-events']
86
+ };
87
+ let bridge;
88
+ try {
89
+ // Start Discord bridge
90
+ log.info(TAG, 'Starting Discord bridge...');
91
+ bridge = await (0, index_1.createDiscordBridge)(config, subscriber);
92
+ // Wait for Discord to be ready
93
+ await new Promise(resolve => setTimeout(resolve, 3000));
94
+ if (!bridge.isConnected()) {
95
+ log.error(TAG, 'āŒ Discord bridge failed to connect or find channel');
96
+ process.exit(1);
97
+ }
98
+ log.info(TAG, 'āœ… Discord bridge connected and ready\n');
99
+ // Publish test events
100
+ log.info(TAG, 'Publishing test events...\n');
101
+ await sleep(1000);
102
+ // Test 1: Startup event
103
+ log.info(TAG, 'Test 1: Server startup event');
104
+ await publisher.publish('keepkey-events', JSON.stringify({
105
+ type: 'server_startup',
106
+ service: 'pioneer-server',
107
+ level: 'success',
108
+ message: 'Pioneer Server started successfully',
109
+ timestamp: new Date().toISOString(),
110
+ details: {
111
+ port: '9001',
112
+ environment: 'test',
113
+ version: '3.0.0'
114
+ }
115
+ }));
116
+ await sleep(2000);
117
+ // Test 2: Health check
118
+ log.info(TAG, 'Test 2: Health check event');
119
+ await publisher.publish('keepkey-events', JSON.stringify({
120
+ type: 'health_check',
121
+ service: 'pioneer-server',
122
+ level: 'info',
123
+ message: 'Health check passed - all systems operational',
124
+ timestamp: new Date().toISOString(),
125
+ details: {
126
+ status: 'healthy',
127
+ uptime: '24h',
128
+ connections: 42
129
+ }
130
+ }));
131
+ await sleep(2000);
132
+ // Test 3: Warning
133
+ log.info(TAG, 'Test 3: Warning event');
134
+ await publisher.publish('keepkey-events', JSON.stringify({
135
+ type: 'high_latency',
136
+ service: 'pioneer-server',
137
+ level: 'warning',
138
+ message: 'API response time exceeded threshold',
139
+ timestamp: new Date().toISOString(),
140
+ details: {
141
+ endpoint: '/api/v1/balances',
142
+ latency: '5200ms',
143
+ threshold: '2000ms'
144
+ }
145
+ }));
146
+ await sleep(2000);
147
+ // Test 4: Error
148
+ log.info(TAG, 'Test 4: Error event');
149
+ await publisher.publish('keepkey-events', JSON.stringify({
150
+ type: 'database_error',
151
+ service: 'pioneer-server',
152
+ level: 'error',
153
+ message: 'Failed to connect to MongoDB',
154
+ timestamp: new Date().toISOString(),
155
+ details: {
156
+ error: 'Connection timeout',
157
+ retries: 3,
158
+ lastAttempt: new Date().toISOString()
159
+ }
160
+ }));
161
+ await sleep(2000);
162
+ // Test 5: Critical
163
+ log.info(TAG, 'Test 5: Critical event');
164
+ await publisher.publish('keepkey-events', JSON.stringify({
165
+ type: 'service_down',
166
+ service: 'pioneer-server',
167
+ level: 'critical',
168
+ message: '🚨 Service is DOWN - immediate attention required',
169
+ timestamp: new Date().toISOString(),
170
+ details: {
171
+ reason: 'Out of memory',
172
+ affectedUsers: '1000+',
173
+ impact: 'Total service outage'
174
+ }
175
+ }));
176
+ log.info(TAG, '\nāœ… All test events published!');
177
+ log.info(TAG, '\nCheck your Discord #keepkey-events channel to verify messages appeared.\n');
178
+ // Keep running for a bit to see any responses
179
+ log.info(TAG, 'Keeping bridge running for 10 seconds...');
180
+ await sleep(10000);
181
+ log.info(TAG, 'āœ… Test completed successfully!\n');
182
+ }
183
+ catch (error) {
184
+ log.error(TAG, 'āŒ Test failed:', error);
185
+ process.exit(1);
186
+ }
187
+ finally {
188
+ // Cleanup
189
+ if (bridge) {
190
+ await bridge.stop();
191
+ }
192
+ await publisher.quit();
193
+ await subscriber.quit();
194
+ process.exit(0);
195
+ }
196
+ }
197
+ function sleep(ms) {
198
+ return new Promise(resolve => setTimeout(resolve, ms));
199
+ }
200
+ // Run the test
201
+ runTest();
202
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@pioneer-platform/discord",
3
+ "version": "1.0.0",
4
+ "description": "Discord integration for Pioneer platform - Redis to Discord bridge",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "build:watch": "tsc --watch",
10
+ "test": "bun test",
11
+ "clean": "rm -rf lib node_modules"
12
+ },
13
+ "keywords": [
14
+ "discord",
15
+ "redis",
16
+ "bridge",
17
+ "notifications"
18
+ ],
19
+ "author": "KeepKey",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "discord.js": "^14.14.1",
23
+ "ioredis": "^5.0.0",
24
+ "@pioneer-platform/loggerdog": "workspace:*"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.14.1",
28
+ "typescript": "^5.8.3"
29
+ }
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Discord Integration for Pioneer Platform
3
+ *
4
+ * Subscribes to Redis channels and forwards events to Discord channels
5
+ */
6
+
7
+ import { Client, GatewayIntentBits, TextChannel, EmbedBuilder } from 'discord.js';
8
+ import type { Redis } from 'ioredis';
9
+
10
+ const log = require('@pioneer-platform/loggerdog')();
11
+ const TAG = ' | Discord Integration | ';
12
+
13
+ export interface DiscordConfig {
14
+ botToken: string;
15
+ botName?: string;
16
+ defaultChannel: string;
17
+ adminUserId?: string;
18
+ redisChannels: string[];
19
+ }
20
+
21
+ export interface DiscordEvent {
22
+ type: string;
23
+ service?: string;
24
+ level?: 'info' | 'success' | 'warning' | 'error' | 'critical';
25
+ message: string;
26
+ timestamp?: string;
27
+ details?: Record<string, any>;
28
+ // Allow any additional fields
29
+ [key: string]: any;
30
+ }
31
+
32
+ const LEVEL_COLORS = {
33
+ info: 0x3498db, // Blue
34
+ success: 0x2ecc71, // Green
35
+ warning: 0xf39c12, // Orange
36
+ error: 0xe74c3c, // Red
37
+ critical: 0x992d22 // Dark Red
38
+ };
39
+
40
+ const LEVEL_ICONS = {
41
+ info: 'ā„¹ļø',
42
+ success: 'āœ…',
43
+ warning: 'āš ļø',
44
+ error: 'āŒ',
45
+ critical: '🚨'
46
+ };
47
+
48
+ export class DiscordBridge {
49
+ private client: Client;
50
+ private config: DiscordConfig;
51
+ private redis: Redis;
52
+ private isReady: boolean = false;
53
+ private targetChannel?: TextChannel;
54
+ private environment: string;
55
+
56
+ constructor(config: DiscordConfig, redis: Redis) {
57
+ this.config = config;
58
+ this.redis = redis;
59
+ this.environment = process.env.LOCAL_ENV || 'production';
60
+
61
+ // Create Discord client with necessary intents
62
+ this.client = new Client({
63
+ intents: [
64
+ GatewayIntentBits.Guilds,
65
+ GatewayIntentBits.GuildMessages,
66
+ GatewayIntentBits.MessageContent
67
+ ]
68
+ });
69
+
70
+ this.setupEventHandlers();
71
+ }
72
+
73
+ /**
74
+ * Setup Discord client event handlers
75
+ */
76
+ private setupEventHandlers(): void {
77
+ this.client.on('ready', async () => {
78
+ log.info(TAG, `[${this.environment.toUpperCase()}] āœ… Discord bot logged in as ${this.client.user?.tag}`);
79
+ this.isReady = true;
80
+
81
+ // Find the target channel
82
+ await this.findTargetChannel();
83
+
84
+ // Subscribe to Redis channels
85
+ this.subscribeToRedis();
86
+ });
87
+
88
+ this.client.on('error', (error) => {
89
+ log.error(TAG, 'Discord client error:', error);
90
+ });
91
+
92
+ this.client.on('warn', (warning) => {
93
+ log.warn(TAG, 'Discord client warning:', warning);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Find the Discord channel to post to
99
+ */
100
+ private async findTargetChannel(): Promise<void> {
101
+ const tag = TAG + ' | findTargetChannel | ';
102
+
103
+ try {
104
+ // Get all channels
105
+ const channels = this.client.channels.cache;
106
+ log.info(tag, `Searching ${channels.size} cached channels for: ${this.config.defaultChannel}`);
107
+
108
+ // Find by name
109
+ const channel = channels.find((ch: any) =>
110
+ ch.name === this.config.defaultChannel && ch.isTextBased()
111
+ ) as TextChannel;
112
+
113
+ if (channel) {
114
+ this.targetChannel = channel;
115
+ log.info(tag, `āœ… Found target channel: #${channel.name} (${channel.id})`);
116
+ } else {
117
+ log.error(tag, `āŒ Could not find channel: #${this.config.defaultChannel}`);
118
+ log.info(tag, 'Available channels:', channels.map((ch: any) => ch.name).join(', '));
119
+ }
120
+ } catch (error) {
121
+ log.error(tag, 'Error finding target channel:', error);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Subscribe to Redis channels
127
+ */
128
+ private subscribeToRedis(): void {
129
+ const tag = TAG + ' | subscribeToRedis | ';
130
+
131
+ log.info(tag, `Subscribing to Redis channels: ${this.config.redisChannels.join(', ')}`);
132
+
133
+ // Subscribe to all configured channels
134
+ this.config.redisChannels.forEach(channel => {
135
+ this.redis.subscribe(channel, (err) => {
136
+ if (err) {
137
+ log.error(tag, `Failed to subscribe to ${channel}:`, err);
138
+ } else {
139
+ log.info(tag, `āœ… Subscribed to Redis channel: ${channel}`);
140
+ }
141
+ });
142
+ });
143
+
144
+ // Handle incoming messages
145
+ this.redis.on('message', async (channel: string, message: string) => {
146
+ await this.handleRedisMessage(channel, message);
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Handle incoming Redis messages
152
+ */
153
+ private async handleRedisMessage(channel: string, message: string): Promise<void> {
154
+ const tag = TAG + ' | handleRedisMessage | ';
155
+
156
+ try {
157
+ log.debug(tag, `Received message from ${channel}`);
158
+
159
+ // Parse the message
160
+ let event: DiscordEvent;
161
+ try {
162
+ event = JSON.parse(message);
163
+ } catch (parseError) {
164
+ log.error(tag, 'Failed to parse Redis message:', parseError);
165
+ return;
166
+ }
167
+
168
+ // Post to Discord
169
+ await this.postToDiscord(event);
170
+
171
+ } catch (error) {
172
+ log.error(tag, 'Error handling Redis message:', error);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Post event to Discord channel
178
+ */
179
+ private async postToDiscord(event: DiscordEvent): Promise<void> {
180
+ const tag = TAG + ' | postToDiscord | ';
181
+
182
+ if (!this.isReady) {
183
+ log.warn(tag, 'Discord client not ready yet');
184
+ return;
185
+ }
186
+
187
+ if (!this.targetChannel) {
188
+ log.error(tag, 'No target channel found');
189
+ return;
190
+ }
191
+
192
+ try {
193
+ // Determine level
194
+ const level = event.level || 'info';
195
+ const icon = LEVEL_ICONS[level] || 'ā„¹ļø';
196
+ const color = LEVEL_COLORS[level] || LEVEL_COLORS.info;
197
+
198
+ // Create embed
199
+ const embed = new EmbedBuilder()
200
+ .setColor(color)
201
+ .setTitle(`${icon} ${event.type || 'Event'}`)
202
+ .setDescription(event.message)
203
+ .setTimestamp(event.timestamp ? new Date(event.timestamp) : new Date());
204
+
205
+ // Add service field if present
206
+ if (event.service) {
207
+ embed.addFields({ name: 'Service', value: event.service, inline: true });
208
+ }
209
+
210
+ // Add details fields if present
211
+ if (event.details) {
212
+ Object.entries(event.details).forEach(([key, value]) => {
213
+ embed.addFields({
214
+ name: key,
215
+ value: String(value),
216
+ inline: true
217
+ });
218
+ });
219
+ }
220
+
221
+ // Send to Discord
222
+ await this.targetChannel.send({ embeds: [embed] });
223
+ log.info(tag, `āœ… Posted to #${this.targetChannel.name}: ${event.type}`);
224
+
225
+ } catch (error) {
226
+ log.error(tag, 'Failed to post to Discord:', error);
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Start the Discord bridge
232
+ */
233
+ async start(): Promise<void> {
234
+ const tag = TAG + ' | start | ';
235
+
236
+ log.info(tag, `[${this.environment.toUpperCase()}] Starting Discord bridge...`);
237
+ log.info(tag, `[${this.environment.toUpperCase()}] Bot name: ${this.config.botName || 'N/A'}`);
238
+ log.info(tag, `[${this.environment.toUpperCase()}] Target channel: #${this.config.defaultChannel}`);
239
+ log.info(tag, `[${this.environment.toUpperCase()}] Redis channels: ${this.config.redisChannels.join(', ')}`);
240
+
241
+ try {
242
+ await this.client.login(this.config.botToken);
243
+ log.info(tag, 'āœ… Discord bridge started successfully');
244
+ } catch (error) {
245
+ log.error(tag, 'āŒ Failed to start Discord bridge:', error);
246
+ throw error;
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Stop the Discord bridge
252
+ */
253
+ async stop(): Promise<void> {
254
+ const tag = TAG + ' | stop | ';
255
+
256
+ log.info(tag, 'Stopping Discord bridge...');
257
+
258
+ // Unsubscribe from Redis channels
259
+ this.config.redisChannels.forEach(channel => {
260
+ this.redis.unsubscribe(channel);
261
+ });
262
+
263
+ // Destroy Discord client
264
+ this.client.destroy();
265
+ this.isReady = false;
266
+
267
+ log.info(tag, 'āœ… Discord bridge stopped');
268
+ }
269
+
270
+ /**
271
+ * Check if the bridge is ready
272
+ */
273
+ isConnected(): boolean {
274
+ return this.isReady && !!this.targetChannel;
275
+ }
276
+
277
+ /**
278
+ * Get the Discord client (for advanced usage)
279
+ */
280
+ getClient(): Client {
281
+ return this.client;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Create and start a Discord bridge
287
+ */
288
+ export async function createDiscordBridge(
289
+ config: DiscordConfig,
290
+ redis: Redis
291
+ ): Promise<DiscordBridge> {
292
+ const bridge = new DiscordBridge(config, redis);
293
+ await bridge.start();
294
+ return bridge;
295
+ }
296
+
297
+ export default DiscordBridge;
package/src/test.ts ADDED
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Standalone test for Discord integration
5
+ *
6
+ * Tests the Discord bridge by:
7
+ * 1. Connecting to Discord
8
+ * 2. Subscribing to keepkey-events Redis channel
9
+ * 3. Publishing test events
10
+ * 4. Verifying they appear in Discord
11
+ *
12
+ * Usage:
13
+ * REDIS_CONNECTION_PROD=redis://... bun run src/test.ts
14
+ */
15
+
16
+ import * as dotenv from 'dotenv';
17
+ import * as path from 'path';
18
+ import Redis from 'ioredis';
19
+ import { createDiscordBridge, DiscordConfig } from './index';
20
+
21
+ // Load environment variables
22
+ dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
23
+
24
+ const log = require('@pioneer-platform/loggerdog')();
25
+ const TAG = ' | Discord Test | ';
26
+
27
+ async function runTest() {
28
+ log.info(TAG, '🧪 Testing Discord Integration\n');
29
+
30
+ // Get config from environment
31
+ const botToken = process.env.KEEPKEY_DISCORD_BOT_TOKEN;
32
+ const botName = process.env.KEEPKEY_BOT_NAME || 'pioneer';
33
+ const channelName = process.env.KEEPKEY_DISCORD_BOT_CHANNEL || 'keepkey-events';
34
+ const redisUrl = process.env.REDIS_CONNECTION_PROD || process.env.REDIS_CONNECTION || 'redis://127.0.0.1:6379';
35
+
36
+ if (!botToken) {
37
+ console.error('āŒ KEEPKEY_DISCORD_BOT_TOKEN not set in environment');
38
+ process.exit(1);
39
+ }
40
+
41
+ log.info(TAG, 'Configuration:');
42
+ log.info(TAG, ` Bot Name: ${botName}`);
43
+ log.info(TAG, ` Channel: #${channelName}`);
44
+ log.info(TAG, ` Redis: ${redisUrl.includes('@') ? redisUrl.split('@')[1] : redisUrl}`);
45
+ log.info(TAG, '');
46
+
47
+ // Create Redis clients
48
+ const subscriber = new Redis(redisUrl);
49
+ const publisher = new Redis(redisUrl);
50
+
51
+ // Discord bridge config
52
+ const config: DiscordConfig = {
53
+ botToken,
54
+ botName,
55
+ defaultChannel: channelName,
56
+ adminUserId: process.env.DISCORD_ADMIN_USERID,
57
+ redisChannels: ['keepkey-events']
58
+ };
59
+
60
+ let bridge;
61
+
62
+ try {
63
+ // Start Discord bridge
64
+ log.info(TAG, 'Starting Discord bridge...');
65
+ bridge = await createDiscordBridge(config, subscriber);
66
+
67
+ // Wait for Discord to be ready
68
+ await new Promise(resolve => setTimeout(resolve, 3000));
69
+
70
+ if (!bridge.isConnected()) {
71
+ log.error(TAG, 'āŒ Discord bridge failed to connect or find channel');
72
+ process.exit(1);
73
+ }
74
+
75
+ log.info(TAG, 'āœ… Discord bridge connected and ready\n');
76
+
77
+ // Publish test events
78
+ log.info(TAG, 'Publishing test events...\n');
79
+
80
+ await sleep(1000);
81
+
82
+ // Test 1: Startup event
83
+ log.info(TAG, 'Test 1: Server startup event');
84
+ await publisher.publish('keepkey-events', JSON.stringify({
85
+ type: 'server_startup',
86
+ service: 'pioneer-server',
87
+ level: 'success',
88
+ message: 'Pioneer Server started successfully',
89
+ timestamp: new Date().toISOString(),
90
+ details: {
91
+ port: '9001',
92
+ environment: 'test',
93
+ version: '3.0.0'
94
+ }
95
+ }));
96
+
97
+ await sleep(2000);
98
+
99
+ // Test 2: Health check
100
+ log.info(TAG, 'Test 2: Health check event');
101
+ await publisher.publish('keepkey-events', JSON.stringify({
102
+ type: 'health_check',
103
+ service: 'pioneer-server',
104
+ level: 'info',
105
+ message: 'Health check passed - all systems operational',
106
+ timestamp: new Date().toISOString(),
107
+ details: {
108
+ status: 'healthy',
109
+ uptime: '24h',
110
+ connections: 42
111
+ }
112
+ }));
113
+
114
+ await sleep(2000);
115
+
116
+ // Test 3: Warning
117
+ log.info(TAG, 'Test 3: Warning event');
118
+ await publisher.publish('keepkey-events', JSON.stringify({
119
+ type: 'high_latency',
120
+ service: 'pioneer-server',
121
+ level: 'warning',
122
+ message: 'API response time exceeded threshold',
123
+ timestamp: new Date().toISOString(),
124
+ details: {
125
+ endpoint: '/api/v1/balances',
126
+ latency: '5200ms',
127
+ threshold: '2000ms'
128
+ }
129
+ }));
130
+
131
+ await sleep(2000);
132
+
133
+ // Test 4: Error
134
+ log.info(TAG, 'Test 4: Error event');
135
+ await publisher.publish('keepkey-events', JSON.stringify({
136
+ type: 'database_error',
137
+ service: 'pioneer-server',
138
+ level: 'error',
139
+ message: 'Failed to connect to MongoDB',
140
+ timestamp: new Date().toISOString(),
141
+ details: {
142
+ error: 'Connection timeout',
143
+ retries: 3,
144
+ lastAttempt: new Date().toISOString()
145
+ }
146
+ }));
147
+
148
+ await sleep(2000);
149
+
150
+ // Test 5: Critical
151
+ log.info(TAG, 'Test 5: Critical event');
152
+ await publisher.publish('keepkey-events', JSON.stringify({
153
+ type: 'service_down',
154
+ service: 'pioneer-server',
155
+ level: 'critical',
156
+ message: '🚨 Service is DOWN - immediate attention required',
157
+ timestamp: new Date().toISOString(),
158
+ details: {
159
+ reason: 'Out of memory',
160
+ affectedUsers: '1000+',
161
+ impact: 'Total service outage'
162
+ }
163
+ }));
164
+
165
+ log.info(TAG, '\nāœ… All test events published!');
166
+ log.info(TAG, '\nCheck your Discord #keepkey-events channel to verify messages appeared.\n');
167
+
168
+ // Keep running for a bit to see any responses
169
+ log.info(TAG, 'Keeping bridge running for 10 seconds...');
170
+ await sleep(10000);
171
+
172
+ log.info(TAG, 'āœ… Test completed successfully!\n');
173
+
174
+ } catch (error: any) {
175
+ log.error(TAG, 'āŒ Test failed:', error);
176
+ process.exit(1);
177
+ } finally {
178
+ // Cleanup
179
+ if (bridge) {
180
+ await bridge.stop();
181
+ }
182
+ await publisher.quit();
183
+ await subscriber.quit();
184
+ process.exit(0);
185
+ }
186
+ }
187
+
188
+ function sleep(ms: number): Promise<void> {
189
+ return new Promise(resolve => setTimeout(resolve, ms));
190
+ }
191
+
192
+ // Run the test
193
+ runTest();
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./lib",
7
+ "rootDir": "./src",
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "strict": true,
14
+ "resolveJsonModule": true,
15
+ "moduleResolution": "node"
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "lib", "**/*.test.ts"]
19
+ }