I am trying to use the latest version of Swift package socket.io-client-swift to connect to my socket.io server built in Node.js, but no connection ever seems to be made, because nothing is logged in the console indicating that a connection was successfully made - no polling, no established handshake.
These are the logs that occur in the app indicating that a connection cannot be established:
LOG SocketIOClient{/}: Adding handler for event: connect
LOG SocketIOClient{/}: Adding handler for event: disconnect
LOG SocketIOClient{/}: Adding handler for event: error
LOG SocketIOClient{/}: Handling event: statusChange with data: [connecting, 2]
LOG SocketIOClient{/}: Joining namespace /
LOG SocketManager: Tried connecting socket when engine isn't open. Connecting
LOG SocketManager: Adding engine
LOG SocketEngine: Starting engine. Server: https://ws.my-socket-server.com
LOG SocketEngine: Handshaking
LOG SocketManager: Manager is being released
LOG SocketIOClient{/}: Adding handler for event: connect
LOG SocketIOClient{/}: Adding handler for event: disconnect
LOG SocketIOClient{/}: Adding handler for event: error
LOG SocketIOClient{/}: Handling event: statusChange with data: [connecting, 2]
LOG SocketIOClient{/}: Joining namespace /
LOG SocketManager: Tried connecting socket when engine isn't open. Connecting
LOG SocketManager: Tried connecting an already active socket
As you can see, it says "Tried connecting socket when engine isn't open" & releases the Manager very early.
Here is my singleton WebSocketManager I am creating to share the socket with all of my views:
import Foundation
import SocketIO
class WebSocketManager: NSObject, ObservableObject, URLSessionWebSocketDelegate {
static let shared = WebSocketManager()
@Published var drawingSocket: SocketIOClient?
private var drawingSocketManager: SocketManager?
private let drawingSocketURL = URL(string: "https://ws.my-socket-server.com")!
override private init() {
super.init()
setupDrawingSocket()
}
private func setupDrawingSocket() {
drawingSocketManager = SocketManager(socketURL: drawingSocketURL, config: [.log(true), .reconnectAttempts(5), .reconnectWaitMax(5), .forceWebsockets(true)])
drawingSocket = drawingSocketManager?.defaultSocket
drawingSocket?.on(clientEvent: .connect) { _, _ in
print("Drawing socket connected")
}
drawingSocket?.on(clientEvent: .disconnect) { data, _ in
print("Drawing socket disconnected: \(data)")
}
drawingSocket?.on(clientEvent: .error) { data, _ in
print("Drawing socket error: \(data)")
}
if drawingSocket?.status != .connected {
drawingSocket?.connect()
}
}
func sendDrawingMessage(_ action: String, data: Any) {
guard let drawingSocket = drawingSocket else {
print("Drawing socket is not available")
return
}
// Check if the socket is connected before sending a message
if drawingSocket.status == .connected {
drawingSocket.emit(action, data as! SocketData)
} else {
print("Drawing socket is not connected. Cannot send message.")
}
}
var isDrawingSocketConnected: Bool {
return drawingSocket?.status == .connected
}
}
Here is the entrypoint to all of my views:
import SwiftUI
@main
struct my_iosApp: App {
var body: some Scene {
WindowGroup {
Home()
.environmentObject(WebSocketManager.shared)
.environmentObject(GlobalStateManager.shared)
}
}
}
and finally, here is where I am using the socket to emit a message to the server:
import SocketIO
import SwiftUI
struct GameCodeDisplay: View {
@EnvironmentObject var globalStateManager: GlobalStateManager
@EnvironmentObject var webSocketManager: WebSocketManager
let gameCode: String
var onCancel: () -> Void
private var drawingSocket: SocketIOClient
init(gameCode: String, onCancel: @escaping () -> Void) {
self.gameCode = gameCode
self.onCancel = onCancel
let manager = SocketManager(socketURL: URL(string: "https://ws.my-socket-server.com")!, config: [.log(true), .reconnectAttempts(5), .reconnectWaitMax(5), .forceWebsockets(true)])
self.drawingSocket = manager.defaultSocket
}
var body: some View {
ZStack {
Color(red: 115.0 / 255.0, green: 5.0 / 255.0, blue: 60.0 / 255.0)
.edgesIgnoringSafeArea(.all)
VStack {
Spacer() // Pushes content to the center
Text("Your game code is:")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.white)
Text(gameCode)
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.white)
.padding(.bottom)
ScrollView {
VStack {
ForEach(globalStateManager.players, id: \.self) { player in
Text(player)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
.foregroundColor(.white)
}
}
}
.frame(maxHeight: 200) // Adjust as needed
if globalStateManager.players.count >= 5 {
Button("Start!") {
startGame(drawingSocket: drawingSocket, gameCode: gameCode)
}
.disabled(globalStateManager.players.count < 5)
.padding()
.foregroundColor(.green)
}
Button("Cancel") {
onCancel()
leaveGame(webSocketManager: webSocketManager, globalStateManager: globalStateManager)
}
.padding()
.foregroundColor(.yellow)
Spacer() // Allows the button to be at the bottom
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding() // Add padding around the entire VStack
}.onAppear {
setupDrawingSocketEvents()
}
}
private func setupDrawingSocketEvents() {
guard let drawingSocket = webSocketManager.drawingSocket else {
print("Drawing socket not available")
return
}
drawingSocket.on(clientEvent: .connect) { _, _ in
print("Drawing socket connected")
// Additional code for when the socket connects
}
drawingSocket.on(clientEvent: .disconnect) { data, _ in
print("Drawing socket disconnected: \(data)")
// Additional code for when the socket disconnects
}
drawingSocket.on(clientEvent: .error) { data, _ in
print("Drawing socket error: \(data)")
// Additional error handling
}
// Connect to the socket if not connected
if !webSocketManager.isDrawingSocketConnected {
drawingSocket.connect()
}
}
private func startGame(drawingSocket: SocketIOClient, gameCode: String) {
print("GameCodeDisplay: Starting game with gameCode - \(gameCode)")
webSocketManager.sendDrawingMessage("joinGame", data: gameCode)
webSocketManager.sendDrawingMessage("startGame", data: gameCode)
}
private func leaveGame(webSocketManager: WebSocketManager, globalStateManager: GlobalStateManager) {
print("GameCodeDisplay: Leaving game with gameCode - \(gameCode)")
webSocketManager.sendLeaveGameMessage(gameCode: gameCode, username: globalStateManager.username)
webSocketManager.disconnect()
globalStateManager.players.removeAll()
globalStateManager.setUsername(usernameToSet: "")
}
}
Here is Node.js server that the socket is running on. I am using version socket.io: "^4.7.2":
const express = require("express");
const { Server } = require("socket.io");
const cors = require("cors");
const http = require("http");
const allowedOrigins = [
"https://website-1.com",
"https://website-2.com",
"http://localhost:3000",
];
const app = express();
app.use(
cors({
origin: function (origin, callback) {
console.log("origin: ", origin);
if (!origin) return callback(null, true); // Allow requests with no origin (like mobile apps or curl requests)
if (allowedOrigins.indexOf(origin) === -1) {
const msg =
"The CORS policy for this site does not allow access from the specified Origin.";
return callback(new Error(msg), false);
}
return callback(null, true);
},
})
);
app.get("/health-check", (req, res) => res.status(200).send("OK"));
const server = http.createServer(app);
const io = new Server(server, {
transports: ["websocket"],
cors: {
origin: function (origin, callback) {
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
methods: ["GET", "POST"],
},
});
const connectedSockets = new Set();
io.on("connection", (socket) => {
connectedSockets.add(socket);
socket.on("joinGame", (gameCode, callback) => {
socket.join(gameCode);
console.log(`Socket with ID ${socket.id} joined room: ${gameCode}`);
if (typeof callback === "function") {
callback(`Joined room: ${gameCode}`);
}
});
socket.on("startGame", (gameCode) => {
console.log("Emitting gameStarted to room:", gameCode);
io.to(gameCode).emit("gameStarted");
});
});
server.listen(3001, (err) => {
connectedSockets.forEach((socket) => {
socket.disconnect(true);
});
connectedSockets.clear();
if (err) throw err;
console.log("> Ready on http://localhost:3001");
});
I have tried a variety of different ways of creating that singleton I mentioned of the WebSocketManager to no avail. At this point, I am open to any other Swift package alternatives to connect to my socket.io server from my SwiftUI iOS app.