buster/packages/slack
dal 5e7467aefc
fix: improve Slack messaging reliability and error handling
- Add proper error handling for Slack API failures with typed responses
- Implement message operation types for better type safety
- Add retry logic with exponential backoff for transient failures
- Export webhook types for external consumers
- Update Slack agent task to handle errors gracefully and continue processing
- Add proper validation and error messages for failed operations
- Include structured error tracking for debugging

🤖 Generated with Anthropic

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-18 20:36:29 -06:00
..
scripts Use tsx and .ts files for validation 2025-07-21 16:07:14 -06:00
src fix: improve Slack messaging reliability and error handling 2025-08-18 20:36:29 -06:00
.env.example env vars 2025-07-14 15:12:46 -06:00
.gitignore Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
CLAUDE.md Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
README.md Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
biome.json Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00
env.d.ts env vars 2025-07-14 15:12:46 -06:00
package.json turbo fast web build 2025-08-11 11:33:18 -06:00
tsconfig.json fix all of the bugs 2025-07-12 22:14:08 -06:00
vitest.config.ts Mastra braintrust (#391) 2025-07-02 14:33:40 -07:00

README.md

@buster/slack

Standalone Slack integration package with OAuth 2.0, messaging, and channel management capabilities.

Installation

pnpm add @buster/slack

Features

  • OAuth 2.0 Authentication - Complete OAuth flow with CSRF protection
  • Channel Management - List, validate, join, and leave channels
  • Messaging - Send messages with blocks, attachments, and threading support
  • Type Safety - Full TypeScript support with no any types
  • Zod Validation - Runtime validation for all inputs
  • Dependency Injection - Easy testing with injectable WebClient
  • Framework Agnostic - Works with any Node.js framework
  • Storage Interfaces - Bring your own storage implementation
  • Retry Logic - Automatic retry with exponential backoff
  • Error Handling - Typed errors with user-friendly messages

Usage

OAuth Authentication

import { SlackAuthService, ISlackTokenStorage, ISlackOAuthStateStorage } from '@buster/slack';

// Implement storage interfaces
const tokenStorage: ISlackTokenStorage = { /* your implementation */ };
const stateStorage: ISlackOAuthStateStorage = { /* your implementation */ };

const authService = new SlackAuthService(
  {
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    redirectUri: 'https://your-app.com/slack/callback',
    scopes: ['channels:read', 'chat:write'],
  },
  tokenStorage,
  stateStorage
);

// Generate OAuth URL
const { authUrl, state } = await authService.generateAuthUrl({ userId: 'user-123' });

// Handle OAuth callback
const result = await authService.handleCallback(code, state, 'user-123');

Channel Management

import { SlackChannelService } from '@buster/slack';

const channelService = new SlackChannelService();

// Get available channels
const channels = await channelService.getAvailableChannels(accessToken);

// Validate channel access
const channel = await channelService.validateChannelAccess(accessToken, channelId);

// Join a channel
const { success } = await channelService.joinChannel(accessToken, channelId);

// Leave a channel
const leaveResult = await channelService.leaveChannel(accessToken, channelId);

Sending Messages

import { SlackMessagingService } from '@buster/slack';

const messagingService = new SlackMessagingService();

// Send a simple message
const result = await messagingService.sendMessage(
  accessToken,
  channelId,
  { text: 'Hello, Slack!' }
);

// Send a message with blocks
const richMessage = await messagingService.sendMessage(
  accessToken,
  channelId,
  {
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: '*Important Update*\nThis is a formatted message.',
        },
      },
    ],
  }
);

// Send with automatic retry
const retryResult = await messagingService.sendMessageWithRetry(
  accessToken,
  channelId,
  { text: 'Important notification' },
  3 // max retries
);

Threading and Replies

// Send initial message
const { messageTs } = await messagingService.sendMessage(
  accessToken,
  channelId,
  { text: 'Deployment started...' }
);

// Reply to the message
await messagingService.replyToMessage(
  accessToken,
  channelId,
  messageTs,
  { text: 'Deployment completed successfully!' }
);

Message Tracking

Implement the ISlackMessageTracking interface to enable message tracking for threading:

import { ISlackMessageTracking, MessageTrackingData } from '@buster/slack';

class MyMessageTracking implements ISlackMessageTracking {
  async storeMessageTracking(trackingData: MessageTrackingData): Promise<void> {
    // Store message mapping in your database
  }
  
  async getMessageTracking(internalMessageId: string): Promise<MessageTrackingData | null> {
    // Retrieve message mapping
  }
  
  // ... other methods
}

Message Formatting Utilities

import { 
  formatSimpleMessage, 
  formatBlockMessage,
  createSectionBlock,
  createActionsBlock,
  createContextBlock,
  createDividerBlock,
  MessageTemplates 
} from '@buster/slack';

// Simple message
const message = formatSimpleMessage('Hello, Slack!');

// Message with blocks
const blockMessage = formatBlockMessage([
  createSectionBlock('*Important Announcement*', { type: 'mrkdwn' }),
  createDividerBlock(),
  createContextBlock(['Posted by bot at ' + new Date().toISOString()])
]);

// Deployment notification template
const deploymentMessage = MessageTemplates.deployment({
  project: 'my-app',
  environment: 'production',
  version: '1.2.3',
  status: 'success',
  duration: '2m 30s',
  url: 'https://example.com/deployments/123'
});

// Alert message template
const alertMessage = MessageTemplates.alert({
  title: 'High CPU Usage',
  message: 'CPU usage exceeded 90% threshold',
  severity: 'warning',
  source: 'monitoring-system',
  actions: [
    { text: 'View Dashboard', url: 'https://example.com/dashboard' }
  ]
});

// Review flagging template
const reviewMessage = MessageTemplates.reviewFlag({
  reviewerName: 'Jane Doe',
  profileUrl: 'https://example.com/profile/jane',
  issueTitle: 'Data Quality Issue',
  description: 'The query returned unexpected results that may indicate a data integrity problem.'
});

// Update and delete messages
await messagingService.updateMessage(accessToken, channelId, messageTs, {
  text: 'Updated message content'
});

await messagingService.deleteMessage(accessToken, channelId, messageTs);

Testing

The package includes comprehensive test coverage and supports dependency injection for easy testing:

import { SlackMessagingService } from '@buster/slack';
import { WebClient } from '@slack/web-api';

// Create a mock WebClient for testing
const mockClient = {
  chat: {
    postMessage: jest.fn().mockResolvedValue({ ok: true, ts: '123' })
  }
} as unknown as WebClient;

// Inject the mock client
const messagingService = new SlackMessagingService(mockClient);

// Test your integration
const result = await messagingService.sendMessage(
  'test-token',
  'C123',
  { text: 'Test message' }
);

Error Handling

All services provide typed errors for different scenarios:

import { SlackIntegrationError } from '@buster/slack';

try {
  await messagingService.sendMessage(token, channelId, message);
} catch (error) {
  if (error instanceof SlackIntegrationError) {
    switch (error.code) {
      case 'INVALID_TOKEN':
        // Handle invalid token
        break;
      case 'CHANNEL_NOT_FOUND':
        // Handle channel not found
        break;
      case 'RATE_LIMITED':
        // Handle rate limiting
        break;
    }
  }
}

Development

# Install dependencies
pnpm install

# Type check
pnpm run typecheck

# Build
pnpm run build

# Test
pnpm run test

# Lint and format
pnpm run check:fix

Integration Testing

Want to test against your real Slack workspace? It's super easy:

  1. Create a Slack app at https://api.slack.com/apps
  2. Copy .env.example to .env
  3. Add just 2 values:
    • SLACK_BOT_TOKEN - Your bot token from OAuth & Permissions page
    • SLACK_CHANNEL_ID - Any channel ID (right-click channel → View details)
  4. Run the tests:
    pnpm run test:integration
    

That's it! The integration tests will:

  • Validate your bot token
  • Check channel access
  • Send test messages
  • Test message updates
  • Test threading
  • List available channels
  • Test OAuth flows
  • Test error handling

All tests run in seconds and show clear pass/fail results. Integration tests are in .int.test.ts files alongside the service files.

Architecture

This package follows a clean architecture with:

  • Services - Core business logic (auth, channels, messaging)
  • Interfaces - Storage contracts for tokens and state
  • Types - TypeScript types and Zod schemas
  • Utils - Helper functions and formatters
  • Mocks - Testing utilities

All code is strictly typed with no any or unknown types, ensuring type safety throughout your application.