Reputation: 41
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