Samuel Justice
Samuel Justice

Reputation: 41

Socket Server client connecting but not receiving requests

I am using Starscream and Swift to create a client application, it is able to connect to the server and ping the server, and the server says it is able to send requests to the client - however my client is not picking them up.

My Swift code -

import Foundation
import Starscream
import UniformTypeIdentifiers

class HelperWebSocket: WebSocketDelegate {
    static let shared = HelperWebSocket()
    
    private var serverWebSocketURL: URL {
        if let jsonServerURLString = SettingsManager.shared.jsonServerURL,
           let url = URL(string: jsonServerURLString) {
            print("HelperWebSocket: Server URL fetched from SettingsManager: \(url)")
            return url
        } else {
            print("HelperWebSocket: Server URL not found in SettingsManager, using default URL.")
            return URL(string: "wss://*******:65500/")!
        }
    }
    
    private var socket: WebSocket! {
        didSet {
            print("HelperWebSocket: WebSocket delegate set.")
            socket.delegate = self
        }
    }
    
    private var heartbeatTimer: Timer?
    
    private init() {
            
        print("HelperWebSocket: Inside init, about to initialize socket.")
        
        socket = WebSocket(request: URLRequest(url: serverWebSocketURL), certPinner: FoundationSecurity(allowSelfSigned: false))
        // Disable SSL Certificate Validation
        //socket.disableSSLCertValidation = true

        print("HelperWebSocket: Socket initialized, about to connect.")
        socket.connect()
        print("HelperWebSocket: Socket connection initiated.")
        startHeartbeat()
        print("HelperWebSocket: Heartbeat started.")
    }

    
    // MARK: - WebSocketDelegate methods
    
    func didReceive(event: WebSocketEvent, client: WebSocketClient) {
        print("HelperWebSocket: Received WebSocket event: \(event)")
        switch event {
        case .connected(let headers):
            print("HelperWebSocket: Successfully connected to server with headers: \(headers)")
            print("HelperWebSocket: Heartbeat will keep the connection alive.")
        case .disconnected(let reason, let code):
            print("HelperWebSocket: Disconnected from server. Reason: \(reason), Code: \(code)")
            print("HelperWebSocket: Stopping heartbeat.")
            stopHeartbeat()
        case .pong:
            print("HelperWebSocket: Received PONG from server. Heartbeat acknowledged.")
        case .text(let text):
            print("HelperWebSocket: Received text: \(text)")
            
            if let command = try? JSONDecoder().decode(ServerCommand.self, from: Data(text.utf8)) {
                print("HelperWebSocket: Successfully decoded the command: \(command)")
                handleCommand(command)
            } else {
                print("HelperWebSocket: Failed to decode the incoming command.")
            }
            
        default:
            print("HelperWebSocket: Unhandled WebSocket event.")
            break
        }
    }

    
    // MARK: - Command Handling
    
    private func handleCommand(_ command: ServerCommand) {
        switch command.type {
        case .listDirectory, .upload, .genericCommand:
            guard let allowedCommand = command.command else {
                NSLog("HelperWebSocket: Command not provided.")
                return
            }
            
            helperController.runCommand(allowedCommand) { (result) in
                DispatchQueue.main.async {
                    switch result {
                    case .success(let reply):
                        let response = ServerResponse(type: .commandOutput, data: reply.standardOutput?.components(separatedBy: "\n"))
                        self.sendResponse(response)
                    case .failure(let error):
                        NSLog("HelperWebSocket: Failed to handle command: \(error)")
                        self.sendErrorResponse(error: error)
                    }
                }
            }
            
        case .hello:
            let helloResponse = ServerResponse(type: .commandOutput, data: ["Hello back from the client"])
            sendResponse(helloResponse)
        }
    }

    
    private func sendErrorResponse(error: Error) {
        let errorResponse = ServerResponse(error: error)
        sendResponse(errorResponse)
    }
    
    private func sendResponse(_ response: ServerResponse) {
        do {
            let responseData = try JSONEncoder().encode(response)
            if let jsonString = String(data: responseData, encoding: .utf8) {
                socket.write(string: jsonString)
            }
        } catch {
            NSLog("HelperWebSocket: Failed to encode and send response: \(error)")
        }
    }
    
    // MARK: - Heartbeat
    
    private func startHeartbeat() {
        DispatchQueue.main.async { [weak self] in
            print("HelperWebSocket: Starting heartbeat.")
            self?.heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 120, repeats: true) { [weak self] _ in
                self?.sendHeartbeat()
            }
        }
    }
    
    private func stopHeartbeat() {
        print("HelperWebSocket: Stopping heartbeat.")
        heartbeatTimer?.invalidate()
        heartbeatTimer = nil
    }
    
    private func sendHeartbeat() {
        print("HelperWebSocket: Sending heartbeat (PING) to server.")
        
        socket.write(ping: Data())
    }
    
    // MARK: - Data Models
    
    private struct ServerCommand: Codable {
        enum CommandType: String, Codable {
            case listDirectory
            case upload
            case genericCommand
            case hello
        }
        var type: CommandType
        var path: String?
        var url: URL?
        var command: AllowedCommand?
    }
    
    private struct ServerResponse: Codable {
        enum ResponseType: String, Codable {
            case commandOutput
            case error
        }
        
        var type: ResponseType
        var data: [String]?
        var errorMessage: String?
        
        init(type: ResponseType, data: [String]?) {
            self.type = type
            self.data = data
            self.errorMessage = nil
        }
        
        init(error: Error) {
            self.type = .error
            self.errorMessage = "\(error)"
            self.data = nil
        }
    }
}

// MARK: - URL Extension for MIME Type

extension URL {
    func mimeType() -> String {
        let pathExtension = self.pathExtension
        if let type = UTType(filenameExtension: pathExtension) {
            return type.preferredMIMEType ?? "application/octet-stream"
        }
        return "application/octet-stream"
    }
}

and my Server code -

const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const path = require('path');
const https = require('https');

const app = express();
const fs = require('fs');

const credentials = {
  key: fs.readFileSync(path.join(__dirname, 'private.key'), 'utf8'),
  ca: fs.readFileSync(path.join(__dirname, 'ca_bundle.crt'), 'utf8'),
  cert: fs.readFileSync(path.join(__dirname, 'certificate.crt'), 'utf8')
};

const server = https.createServer(credentials, app);

const wss = new WebSocket.Server({ server });

let clients = {};

wss.on('connection', (ws, req) => {
    const ip = req.socket.remoteAddress;
    console.log(`[INFO] Client Connected: ${ip}`);
    clients[ip] = ws;

    // Send a hello message to the client
    ws.send(JSON.stringify({ type: "hello", message: "Hello from the server" }));

    ws.on('message', (message) => {
        try {
            const msgObj = JSON.parse(message);
            switch (msgObj.type) {
                case 'command':
                    console.log(`[COMMAND] Received command from ${ip}: ${msgObj.commandName}`);
                    break;
                case 'error':
                    console.error(`[ERROR] Received error from ${ip}: ${msgObj.errorMessage}`);
                    break;
                default:
                    console.warn(`[WARN] Unknown message type from ${ip}.`);
            }
        } catch (err) {
            console.error(`[ERROR] Failed to parse message from ${ip}: ${message}`);
        }
    });

    ws.on('close', () => {
        console.log(`[INFO] Client Disconnected: ${ip}`);
        delete clients[ip];
    });

    ws.on('error', (err) => {
        console.error(`[ERROR] WebSocket error from ${ip}:`, err);
    });

    ws.on('ping', () => {
        console.log(`[PING] Received ping from ${ip}`);
        ws.pong();  // send a 'pong' back in response to the 'ping'
    });

    ws.on('pong', () => {
        console.log(`[PONG] Received pong from ${ip}`);
    });
});

app.get('/clients', (req, res) => {
    console.log('[REQUEST] /clients endpoint accessed.');
    res.json({ connectedClients: Object.keys(clients) });
});

app.get('/send-command/:ip', (req, res) => {
    const { ip } = req.params;
    const { command } = req.query;

    console.log(`[INFO] /send-command called. Attempting to send command to ${ip}: ${command}`);

    const client = clients[ip];

    if (!client) {
        console.error(`[ERROR] Client ${ip} not found in connected list.`);
        return res.status(400).json({ error: 'Client not found' });
    }

    // Send command as JSON
    const commandObject = { type: 'command', command: command };

    // Create a timeout for the response
    const responseTimeout = setTimeout(() => {
        client.removeListener('message', messageHandler);
        console.error(`[ERROR] Response from ${ip} timed out.`);
        res.status(500).json({ error: 'Response timed out' });
    }, 10000);

    // Handle the client's response
    const messageHandler = (message) => {
        console.log(`[INFO] Inside messageHandler, received message: ${message}`);
        
        clearTimeout(responseTimeout);
        client.removeListener('message', messageHandler);

        try {
            const response = JSON.parse(message);
            if (response.type && response.type === 'error') {
                console.error(`[ERROR] Client ${ip} responded with an error: ${response.errorMessage}`);
                return res.status(400).json({ error: response.errorMessage });
            }

            console.log(`[INFO] Received valid response from ${ip}: ${message}`);
            res.json({ result: response });
        } catch (err) {
            console.error(`[ERROR] Failed to parse response from ${ip}: ${message}`);
            res.status(500).json({ error: 'Failed to parse response' });
        }
    };

    client.on('message', messageHandler);

    client.send(JSON.stringify(commandObject), (err) => {
        if (err) {
            console.log(`[ERROR] Inside client.send callback, an error occurred.`);
            
            clearTimeout(responseTimeout);
            client.removeListener('message', messageHandler);
            console.error(`[ERROR] Failed to send command to ${ip}:`, err);
            return res.status(500).json({ error: 'Failed to send command' });
        }
        
        console.log(`[INFO] Command sent successfully to ${ip}. Waiting for response...`);
    });
});

const PORT = 65500;
server.listen(PORT, () => {
    console.log(`[INFO] Server is running on https://localhost:${PORT}`);
});

I am able to send commands - Server log

[INFO] /send-command called. Attempting to send command to ::ffff:*: listContentsOfFolder /Users
[INFO] Command sent successfully to ::ffff:*. Waiting for response...
[ERROR] Response from ::ffff:* timed out.

However the client application should receive these commands and it is not

Upvotes: 1

Views: 129

Answers (0)

Related Questions