stackblitz / webcontainer-core

Dev environments. In your web app.
https://webcontainers.io
MIT License
3.86k stars 156 forks source link

"Error: socket hang up" on attempting to connect to Slack API #1548

Open scrollinondubs opened 5 hours ago

scrollinondubs commented 5 hours ago

Describe the bug

I've been attempting to use Bolt.new to create a Slack Bot that's essentially a clone of this app. Unfortunately it's reached an impasse with this socket hang up error on connecting to the Slack API. I've verified my SLACK_BOT_TOKEN environment var is correct and confirmed i'm able to connect successfully when running the test scripts from my local machine. It's almost as if there is an outbound firewall at StackBlitz that's killing the connection or maybe Slack has a firewall blocking inbound requests from StackBlitz.

Link to the blitz that caused the error

https://bolt.new/~/sb1-vgy2k2

Steps to reproduce

  1. open the terminal and type "node test-client.js"
  2. there are a ton of test scripts that Bolt has created in attempting to troubleshoot this connectivity issue. You'll find them all in the root of the project and they check various pieces of the connectivity stack.

Expected behavior

The expected behavior is that the bot should write a "Hello from Slack Bot!" message in the channel but instead it's producing the "Error: socket hang up" response.

Parity with Local

Screenshots

Confirmed I have created a node.js environment on my local machine just now and was able to recreate these files locally and run the project and get the bot to successfully connect and post in my channel: .env slack-client.js test-client.js

Screenshot 2024-10-23 at 1 00 52 PM

This would indicate to me that it's a connectivity issue unique to the Webcontainer environment (or potentially an issue with the Slack API recognizing and blocking requests from StackBlitz).

Platform

Additional context

I don't know if you're able to review my chat history within Bolt but you'll see a ton of context there for me iterating with the AI on troubleshooting this. This is a head-scratcher. My current thinking is to try and insert some intermediary like a Cloudflare worker that acts as a proxy. If this is truly a firewall issue then hopefully my app can deal with CF and CF can connect to Slack but that's not ideal and not really tenable if this app is to be general use in the Slack marketplace. thanks for your help.

scrollinondubs commented 5 hours ago

Oh yea and here are the two test files to reproduce this in a local environment:

slack-client.js

const https = require('https');
const EventEmitter = require('events');

class SlackClient extends EventEmitter {
  constructor(token, options = {}) {
    super();
    this.token = token;
    this.retries = options.retries || 3;
    this.retryDelay = options.retryDelay || 1000;
    this.timeout = options.timeout || 5000;
  }

  async request(path, method = 'GET', body = null) {
    let lastError;

    for (let attempt = 1; attempt <= this.retries; attempt++) {
      try {
        const result = await this._makeRequest(path, method, body);
        return result;
      } catch (error) {
        lastError = error;
        this.emit('requestError', { error, attempt, remaining: this.retries - attempt });

        if (attempt < this.retries) {
          await new Promise(resolve => setTimeout(resolve, this.retryDelay));
        }
      }
    }

    throw lastError;
  }

  _makeRequest(path, method, body) {
    return new Promise((resolve, reject) => {
      const options = {
        hostname: 'slack.com',
        port: 443,
        path: `/api/${path}`,
        method,
        headers: {
          'Accept': '*/*',
          'User-Agent': 'Node.js',
          'Connection': 'close',
          'Authorization': `Bearer ${this.token}`
        },
        timeout: this.timeout,
        rejectUnauthorized: false
      };

      if (body) {
        options.headers['Content-Type'] = 'application/json';
      }

      const req = https.request(options, (res) => {
        let data = '';
        res.setEncoding('utf8');

        res.on('data', chunk => data += chunk);

        res.on('end', () => {
          try {
            const response = JSON.parse(data);
            if (!response.ok) {
              reject(new Error(response.error || 'Slack API error'));
            } else {
              resolve(response);
            }
          } catch (e) {
            reject(new Error(`Invalid JSON response: ${e.message}`));
          }
        });
      });

      req.on('error', reject);

      req.on('timeout', () => {
        req.destroy();
        reject(new Error('Request timed out'));
      });

      if (body) {
        req.write(JSON.stringify(body));
      }

      req.end();
    });
  }

  async test() {
    return this.request('api.test', 'POST', {});
  }

  async postMessage(channel, text) {
    return this.request('chat.postMessage', 'POST', { channel, text });
  }
}

module.exports = SlackClient;

test-client.js

const dotenv = require('dotenv');
const SlackClient = require('./slack-client');

dotenv.config();

const token = process.env.SLACK_BOT_TOKEN;

if (!token) {
  console.error('Error: SLACK_BOT_TOKEN is not set');
  process.exit(1);
}

async function runTest() {
  const client = new SlackClient(token, {
    retries: 3,
    retryDelay: 1000,
    timeout: 5000
  });

  // Listen for request errors
  client.on('requestError', ({ error, attempt, remaining }) => {
    console.log(`Attempt ${attempt} failed. ${remaining} retries remaining.`);
    console.log(`Error: ${error.message}`);
  });

  try {
    console.log('Testing Slack API connection...');
    const result = await client.test();
    console.log('API Test successful:', result);

    if (process.env.SLACK_CHANNEL_ID) {
      console.log('\nTesting message posting...');
      const msgResult = await client.postMessage(
        process.env.SLACK_CHANNEL_ID,
        'Hello from Slack Bot!'
      );
      console.log('Message posted:', msgResult);
    }
  } catch (error) {
    console.error('All retry attempts failed');
    console.error('Final error:', error.message);
  }
}

runTest();

In your .env file you need to include:

SLACK_BOT_TOKEN=xoxb-your-token-here
SLACK_CHANNEL_ID=your-channel-id