add socket.io
This commit is contained in:
		
							parent
							
								
									24f8ef9c55
								
							
						
					
					
						commit
						ee17456dcd
					
				
							
								
								
									
										159
									
								
								index2.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								index2.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,159 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <title>Socket.IO Test Client</title>
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        body { font-family: sans-serif; display: flex; flex-direction: column; max-width: 800px; margin: auto; }
 | 
				
			||||||
 | 
					        .controls, .message-box { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; border-radius: 5px; }
 | 
				
			||||||
 | 
					        input[type="text"] { width: calc(100% - 100px); padding: 8px; margin-right: 5px; }
 | 
				
			||||||
 | 
					        button { padding: 8px 12px; }
 | 
				
			||||||
 | 
					        #messages { border: 1px solid #eee; padding: 10px; height: 300px; overflow-y: scroll; background-color: #f9f9f9; }
 | 
				
			||||||
 | 
					        .msg { margin: 5px 0; padding: 5px; border-radius: 3px; }
 | 
				
			||||||
 | 
					        .server-msg { background-color: #e1f5fe; }
 | 
				
			||||||
 | 
					        .client-msg { background-color: #c8e6c9; text-align: right; }
 | 
				
			||||||
 | 
					        .status-msg { background-color: #fff9c4; text-align: center; font-style: italic; }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <h1>Socket.IO Test Client</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="controls">
 | 
				
			||||||
 | 
					        <h3>Подключение</h3>
 | 
				
			||||||
 | 
					        <input type="text" id="url" placeholder="Сервер URL" value="https://127.0.0.1:5205/">
 | 
				
			||||||
 | 
					        <input type="text" id="token" placeholder="Токен (для auth)" value="my-secret-token-123">
 | 
				
			||||||
 | 
					        <button id="connectBtn">Подключиться</button>
 | 
				
			||||||
 | 
					        <button id="disconnectBtn" disabled>Отключиться</button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="message-box">
 | 
				
			||||||
 | 
					        <h3>Отправка клиентского сообщения</h3>
 | 
				
			||||||
 | 
					        <input type="text" id="message" placeholder="Введите сообщение" disabled>
 | 
				
			||||||
 | 
					        <button id="sendBtn" disabled>Отправить</button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="controls">
 | 
				
			||||||
 | 
					        <h3>Настройки</h3>
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <input type="checkbox" id="invertLogs">
 | 
				
			||||||
 | 
					            Новый сверху (инвертировать лог)
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <h3>Логи <button id="clearLogBtn" style="font-size: 0.8em; float: right;">Очистить</button></h3>
 | 
				
			||||||
 | 
					    <div id="messages"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        const urlInput = document.getElementById('url');
 | 
				
			||||||
 | 
					        const tokenInput = document.getElementById('token');
 | 
				
			||||||
 | 
					        const connectBtn = document.getElementById('connectBtn');
 | 
				
			||||||
 | 
					        const disconnectBtn = document.getElementById('disconnectBtn');
 | 
				
			||||||
 | 
					        const messageInput = document.getElementById('message');
 | 
				
			||||||
 | 
					        const sendBtn = document.getElementById('sendBtn');
 | 
				
			||||||
 | 
					        const messagesDiv = document.getElementById('messages');
 | 
				
			||||||
 | 
					        const clearLogBtn = document.getElementById('clearLogBtn');
 | 
				
			||||||
 | 
					        const invertLogsCheckbox = document.getElementById('invertLogs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let socket = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function logMessage(message, type = 'status') {
 | 
				
			||||||
 | 
					            const ts = new Date().toLocaleTimeString();
 | 
				
			||||||
 | 
					            const newLogEntry = `<div class="msg ${type}-msg">[${ts}] ${message}</div>`;
 | 
				
			||||||
 | 
					            if (invertLogsCheckbox.checked) {
 | 
				
			||||||
 | 
					                messagesDiv.innerHTML = newLogEntry + messagesDiv.innerHTML;
 | 
				
			||||||
 | 
					                messagesDiv.scrollTop = 0;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                messagesDiv.innerHTML += newLogEntry;
 | 
				
			||||||
 | 
					                messagesDiv.scrollTop = messagesDiv.scrollHeight;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function connectToServer() {
 | 
				
			||||||
 | 
					            if (socket) {
 | 
				
			||||||
 | 
					                logMessage('Уже подключены. Сначала отключитесь.', 'status');
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const token = tokenInput.value;
 | 
				
			||||||
 | 
					            const url = urlInput.value || 'https://127.0.0.1:5205/';
 | 
				
			||||||
 | 
					            logMessage('Подключаемся к серверу...', 'status');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            socket = io(url, {
 | 
				
			||||||
 | 
					                auth: {
 | 
				
			||||||
 | 
					                    token: token || null
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Универсальный перехват всех событий
 | 
				
			||||||
 | 
					            socket.onAny((event, ...args) => {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    logMessage(`onAny -> ${event}: ${JSON.stringify(args[0])}`, 'server');
 | 
				
			||||||
 | 
					                } catch (e) {
 | 
				
			||||||
 | 
					                    logMessage(`onAny -> ${event}: <unserializable>`, 'server');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            socket.on('connect', () => {
 | 
				
			||||||
 | 
					                logMessage(`Успешное подключение! SID: ${socket.id}`, 'status');
 | 
				
			||||||
 | 
					                connectBtn.disabled = true;
 | 
				
			||||||
 | 
					                disconnectBtn.disabled = false;
 | 
				
			||||||
 | 
					                messageInput.disabled = false;
 | 
				
			||||||
 | 
					                sendBtn.disabled = false;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            socket.on('disconnect', () => {
 | 
				
			||||||
 | 
					                logMessage('Отключено от сервера.', 'status');
 | 
				
			||||||
 | 
					                connectBtn.disabled = false;
 | 
				
			||||||
 | 
					                disconnectBtn.disabled = true;
 | 
				
			||||||
 | 
					                messageInput.disabled = true;
 | 
				
			||||||
 | 
					                sendBtn.disabled = true;
 | 
				
			||||||
 | 
					                socket = null;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Часто используемые события
 | 
				
			||||||
 | 
					            socket.on('message', (data) => {
 | 
				
			||||||
 | 
					                logMessage(`message: ${JSON.stringify(data)}`, 'server');
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            socket.on('connected', (data) => {
 | 
				
			||||||
 | 
					                logMessage(`connected: ${JSON.stringify(data)}`, 'server');
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            socket.on('chat:new_message', (data) => {
 | 
				
			||||||
 | 
					                logMessage(`chat:new_message: ${JSON.stringify(data)}`, 'server');
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            socket.on('achievement:received', (data) => {
 | 
				
			||||||
 | 
					                logMessage(`achievement:received: ${JSON.stringify(data)}`, 'server');
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            socket.on('achievement:progress', (data) => {
 | 
				
			||||||
 | 
					                logMessage(`achievement:progress: ${JSON.stringify(data)}`, 'server');
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            socket.on('connect_error', (err) => {
 | 
				
			||||||
 | 
					                logMessage(`Ошибка подключения: ${err.message}`, 'status');
 | 
				
			||||||
 | 
					                socket = null;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function sendMessage() {
 | 
				
			||||||
 | 
					            const message = messageInput.value;
 | 
				
			||||||
 | 
					            if (socket && message) {
 | 
				
			||||||
 | 
					                socket.emit('client_message', { data: message });
 | 
				
			||||||
 | 
					                logMessage(`Я: ${message}`, 'client');
 | 
				
			||||||
 | 
					                messageInput.value = '';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connectBtn.addEventListener('click', connectToServer);
 | 
				
			||||||
 | 
					        disconnectBtn.addEventListener('click', () => {
 | 
				
			||||||
 | 
					            if (socket) socket.disconnect();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        sendBtn.addEventListener('click', sendMessage);
 | 
				
			||||||
 | 
					        messageInput.addEventListener('keyup', (event) => {
 | 
				
			||||||
 | 
					            if (event.key === 'Enter') {
 | 
				
			||||||
 | 
					                sendMessage();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        clearLogBtn.addEventListener('click', () => {
 | 
				
			||||||
 | 
					            messagesDiv.innerHTML = '';
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@ -6,6 +6,10 @@
 | 
				
			|||||||
	objectVersion = 77;
 | 
						objectVersion = 77;
 | 
				
			||||||
	objects = {
 | 
						objects = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Begin PBXBuildFile section */
 | 
				
			||||||
 | 
							1A85C6CC2EA6FD73009FA847 /* SocketIO in Frameworks */ = {isa = PBXBuildFile; productRef = 1A85C6CB2EA6FD73009FA847 /* SocketIO */; };
 | 
				
			||||||
 | 
					/* End PBXBuildFile section */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Begin PBXContainerItemProxy section */
 | 
					/* Begin PBXContainerItemProxy section */
 | 
				
			||||||
		1A6D61DB2E7CD04000B9F736 /* PBXContainerItemProxy */ = {
 | 
							1A6D61DB2E7CD04000B9F736 /* PBXContainerItemProxy */ = {
 | 
				
			||||||
			isa = PBXContainerItemProxy;
 | 
								isa = PBXContainerItemProxy;
 | 
				
			||||||
@ -52,6 +56,7 @@
 | 
				
			|||||||
			isa = PBXFrameworksBuildPhase;
 | 
								isa = PBXFrameworksBuildPhase;
 | 
				
			||||||
			buildActionMask = 2147483647;
 | 
								buildActionMask = 2147483647;
 | 
				
			||||||
			files = (
 | 
								files = (
 | 
				
			||||||
 | 
									1A85C6CC2EA6FD73009FA847 /* SocketIO in Frameworks */,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
								runOnlyForDeploymentPostprocessing = 0;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
@ -112,6 +117,7 @@
 | 
				
			|||||||
			);
 | 
								);
 | 
				
			||||||
			name = yobble;
 | 
								name = yobble;
 | 
				
			||||||
			packageProductDependencies = (
 | 
								packageProductDependencies = (
 | 
				
			||||||
 | 
									1A85C6CB2EA6FD73009FA847 /* SocketIO */,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			productName = yobble;
 | 
								productName = yobble;
 | 
				
			||||||
			productReference = 1A6D61CC2E7CD03E00B9F736 /* yobble.app */;
 | 
								productReference = 1A6D61CC2E7CD03E00B9F736 /* yobble.app */;
 | 
				
			||||||
@ -196,6 +202,9 @@
 | 
				
			|||||||
			);
 | 
								);
 | 
				
			||||||
			mainGroup = 1A6D61C32E7CD03E00B9F736;
 | 
								mainGroup = 1A6D61C32E7CD03E00B9F736;
 | 
				
			||||||
			minimizedProjectReferenceProxies = 1;
 | 
								minimizedProjectReferenceProxies = 1;
 | 
				
			||||||
 | 
								packageReferences = (
 | 
				
			||||||
 | 
									1A85C6CA2EA6FC08009FA847 /* XCRemoteSwiftPackageReference "socket" */,
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
			preferredProjectObjectVersion = 77;
 | 
								preferredProjectObjectVersion = 77;
 | 
				
			||||||
			productRefGroup = 1A6D61CD2E7CD03E00B9F736 /* Products */;
 | 
								productRefGroup = 1A6D61CD2E7CD03E00B9F736 /* Products */;
 | 
				
			||||||
			projectDirPath = "";
 | 
								projectDirPath = "";
 | 
				
			||||||
@ -598,6 +607,25 @@
 | 
				
			|||||||
			defaultConfigurationName = Release;
 | 
								defaultConfigurationName = Release;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
/* End XCConfigurationList section */
 | 
					/* End XCConfigurationList section */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Begin XCRemoteSwiftPackageReference section */
 | 
				
			||||||
 | 
							1A85C6CA2EA6FC08009FA847 /* XCRemoteSwiftPackageReference "socket" */ = {
 | 
				
			||||||
 | 
								isa = XCRemoteSwiftPackageReference;
 | 
				
			||||||
 | 
								repositoryURL = "https://github.com/socketio/socket.io-client-swift";
 | 
				
			||||||
 | 
								requirement = {
 | 
				
			||||||
 | 
									kind = exactVersion;
 | 
				
			||||||
 | 
									version = 16.1.1;
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					/* End XCRemoteSwiftPackageReference section */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Begin XCSwiftPackageProductDependency section */
 | 
				
			||||||
 | 
							1A85C6CB2EA6FD73009FA847 /* SocketIO */ = {
 | 
				
			||||||
 | 
								isa = XCSwiftPackageProductDependency;
 | 
				
			||||||
 | 
								package = 1A85C6CA2EA6FC08009FA847 /* XCRemoteSwiftPackageReference "socket" */;
 | 
				
			||||||
 | 
								productName = SocketIO;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					/* End XCSwiftPackageProductDependency section */
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	rootObject = 1A6D61C42E7CD03E00B9F736 /* Project object */;
 | 
						rootObject = 1A6D61C42E7CD03E00B9F736 /* Project object */;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "originHash" : "c9fb241c5f575df8f20b39649006995779013948e60c51c3f85b729f83b054e7",
 | 
				
			||||||
 | 
					  "pins" : [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "identity" : "socket.io-client-swift",
 | 
				
			||||||
 | 
					      "kind" : "remoteSourceControl",
 | 
				
			||||||
 | 
					      "location" : "https://github.com/socketio/socket.io-client-swift",
 | 
				
			||||||
 | 
					      "state" : {
 | 
				
			||||||
 | 
					        "revision" : "42da871d9369f290d6ec4930636c40672143905b",
 | 
				
			||||||
 | 
					        "version" : "16.1.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "identity" : "starscream",
 | 
				
			||||||
 | 
					      "kind" : "remoteSourceControl",
 | 
				
			||||||
 | 
					      "location" : "https://github.com/daltoniam/Starscream",
 | 
				
			||||||
 | 
					      "state" : {
 | 
				
			||||||
 | 
					        "revision" : "c6bfd1af48efcc9a9ad203665db12375ba6b145a",
 | 
				
			||||||
 | 
					        "version" : "4.0.8"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "version" : 3
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								yobble/Network/AuthNotifications.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								yobble/Network/AuthNotifications.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					import Foundation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extension Notification.Name {
 | 
				
			||||||
 | 
					    static let accessTokenDidChange = Notification.Name("accessTokenDidChange")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -69,6 +69,8 @@ final class AuthService {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    UserDefaults.standard.set(username, forKey: "currentUser")
 | 
					                    UserDefaults.standard.set(username, forKey: "currentUser")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    completion(true, nil)
 | 
					                    completion(true, nil)
 | 
				
			||||||
                } catch {
 | 
					                } catch {
 | 
				
			||||||
                    completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
 | 
					                    completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
 | 
				
			||||||
@ -175,6 +177,8 @@ final class AuthService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        UserDefaults.standard.removeObject(forKey: "currentUser")
 | 
					        UserDefaults.standard.removeObject(forKey: "currentUser")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let allUsers = KeychainService.shared.getAllServices()
 | 
					        let allUsers = KeychainService.shared.getAllServices()
 | 
				
			||||||
        for user in allUsers {
 | 
					        for user in allUsers {
 | 
				
			||||||
            let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil
 | 
					            let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil
 | 
				
			||||||
@ -183,6 +187,7 @@ final class AuthService {
 | 
				
			|||||||
            if hasAccessToken && hasRefreshToken {
 | 
					            if hasAccessToken && hasRefreshToken {
 | 
				
			||||||
                UserDefaults.standard.set(user, forKey: "currentUser")
 | 
					                UserDefaults.standard.set(user, forKey: "currentUser")
 | 
				
			||||||
                if AppConfig.DEBUG { print("Logout: переключились на пользователя \(user)") }
 | 
					                if AppConfig.DEBUG { print("Logout: переключились на пользователя \(user)") }
 | 
				
			||||||
 | 
					                NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
 | 
				
			||||||
                completion(true, nil)
 | 
					                completion(true, nil)
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -266,6 +266,8 @@ final class NetworkClient {
 | 
				
			|||||||
                            KeychainService.shared.save(userId, forKey: "userId", service: tokenInfo.login)
 | 
					                            KeychainService.shared.save(userId, forKey: "userId", service: tokenInfo.login)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        self.completeRefresh(success: true)
 | 
					                        self.completeRefresh(success: true)
 | 
				
			||||||
                    } catch {
 | 
					                    } catch {
 | 
				
			||||||
                        self.completeRefresh(success: false)
 | 
					                        self.completeRefresh(success: false)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										135
									
								
								yobble/Services/SocketService.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								yobble/Services/SocketService.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					import Foundation
 | 
				
			||||||
 | 
					#if canImport(SocketIO)
 | 
				
			||||||
 | 
					import SocketIO
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class SocketService {
 | 
				
			||||||
 | 
					    static let shared = SocketService()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private let syncQueue = DispatchQueue(label: "org.yobble.socket.service")
 | 
				
			||||||
 | 
					    private var currentToken: String?
 | 
				
			||||||
 | 
					    private var currentAuthPayload: [String: Any] = [:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #if canImport(SocketIO)
 | 
				
			||||||
 | 
					    private var manager: SocketManager?
 | 
				
			||||||
 | 
					    private var socket: SocketIOClient?
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private init() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func connectForCurrentUser() {
 | 
				
			||||||
 | 
					        syncQueue.async { [weak self] in
 | 
				
			||||||
 | 
					            guard let self else { return }
 | 
				
			||||||
 | 
					            guard let token = self.resolveCurrentAccessToken() else {
 | 
				
			||||||
 | 
					                if AppConfig.DEBUG { print("[SocketService] No access token available, disconnecting") }
 | 
				
			||||||
 | 
					                self.currentToken = nil
 | 
				
			||||||
 | 
					                self.disconnectInternal()
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.connectInternal(with: token)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func connect(withToken token: String) {
 | 
				
			||||||
 | 
					        syncQueue.async { [weak self] in
 | 
				
			||||||
 | 
					            self?.connectInternal(with: token)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func disconnect() {
 | 
				
			||||||
 | 
					        syncQueue.async { [weak self] in
 | 
				
			||||||
 | 
					            guard let self else { return }
 | 
				
			||||||
 | 
					            self.currentToken = nil
 | 
				
			||||||
 | 
					            self.disconnectInternal()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func resolveCurrentAccessToken() -> String? {
 | 
				
			||||||
 | 
					        guard
 | 
				
			||||||
 | 
					            let login = UserDefaults.standard.string(forKey: "currentUser"),
 | 
				
			||||||
 | 
					            !login.isEmpty
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return KeychainService.shared.get(forKey: "access_token", service: login)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func connectInternal(with token: String) {
 | 
				
			||||||
 | 
					        #if canImport(SocketIO)
 | 
				
			||||||
 | 
					        if token == currentToken,
 | 
				
			||||||
 | 
					           let socket,
 | 
				
			||||||
 | 
					           socket.status == .connected || socket.status == .connecting {
 | 
				
			||||||
 | 
					            if AppConfig.DEBUG { print("[SocketService] Already connected with current token") }
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        currentToken = token
 | 
				
			||||||
 | 
					        currentAuthPayload = ["token": token]
 | 
				
			||||||
 | 
					        setupSocket(with: token)
 | 
				
			||||||
 | 
					        socket?.connect(withPayload: currentAuthPayload)
 | 
				
			||||||
 | 
					        #else
 | 
				
			||||||
 | 
					        if AppConfig.DEBUG {
 | 
				
			||||||
 | 
					            print("[SocketService] SocketIO framework not available; skipping connection")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        #endif
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #if canImport(SocketIO)
 | 
				
			||||||
 | 
					    private func setupSocket(with token: String) {
 | 
				
			||||||
 | 
					        guard let baseURL = URL(string: AppConfig.API_SERVER) else {
 | 
				
			||||||
 | 
					            if AppConfig.DEBUG { print("[SocketService] Invalid socket URL: \(AppConfig.API_SERVER)") }
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        disconnectInternal()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let configuration: SocketIOClientConfiguration = [
 | 
				
			||||||
 | 
					            .log(AppConfig.DEBUG),
 | 
				
			||||||
 | 
					            .compress,
 | 
				
			||||||
 | 
					            .secure(AppConfig.PROTOCOL.lowercased() == "https"),
 | 
				
			||||||
 | 
					            .path(AppConfig.SOCKET_PATH),
 | 
				
			||||||
 | 
					            .reconnects(true),
 | 
				
			||||||
 | 
					            .reconnectWait(2),
 | 
				
			||||||
 | 
					            .forceWebsockets(true),
 | 
				
			||||||
 | 
					            .extraHeaders(["Authorization": "Bearer \(token)"]),
 | 
				
			||||||
 | 
					            .connectParams(["token": token])
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let manager = SocketManager(socketURL: baseURL, config: configuration)
 | 
				
			||||||
 | 
					        manager.handleQueue = syncQueue
 | 
				
			||||||
 | 
					        let socket = manager.defaultSocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if AppConfig.DEBUG {
 | 
				
			||||||
 | 
					            socket.onAny { event in
 | 
				
			||||||
 | 
					                print("[SocketService] onAny event=\(event.event) data=\(event.items ?? [])")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        socket.on(clientEvent: .connect) { _, _ in
 | 
				
			||||||
 | 
					            if AppConfig.DEBUG { print("[SocketService] Connected") }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        socket.on(clientEvent: .disconnect) { data, _ in
 | 
				
			||||||
 | 
					            if AppConfig.DEBUG { print("[SocketService] Disconnected: \(data)") }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        socket.on(clientEvent: .error) { data, _ in
 | 
				
			||||||
 | 
					            if AppConfig.DEBUG { print("[SocketService] Error: \(data)") }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.manager = manager
 | 
				
			||||||
 | 
					        self.socket = socket
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func disconnectInternal() {
 | 
				
			||||||
 | 
					        socket?.disconnect()
 | 
				
			||||||
 | 
					        manager?.disconnect()
 | 
				
			||||||
 | 
					        socket = nil
 | 
				
			||||||
 | 
					        manager = nil
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    #else
 | 
				
			||||||
 | 
					    private func disconnectInternal() { }
 | 
				
			||||||
 | 
					    #endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -18,6 +18,7 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
    @Published var isLoggedIn: Bool = false
 | 
					    @Published var isLoggedIn: Bool = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private let authService = AuthService()
 | 
					    private let authService = AuthService()
 | 
				
			||||||
 | 
					    private let socketService = SocketService.shared
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private enum DefaultsKeys {
 | 
					    private enum DefaultsKeys {
 | 
				
			||||||
        static let currentUser = "currentUser"
 | 
					        static let currentUser = "currentUser"
 | 
				
			||||||
@ -38,10 +39,12 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
                if success {
 | 
					                if success {
 | 
				
			||||||
                    self?.loadStoredUser()
 | 
					                    self?.loadStoredUser()
 | 
				
			||||||
                    self?.isLoggedIn = true
 | 
					                    self?.isLoggedIn = true
 | 
				
			||||||
 | 
					                    self?.socketService.connectForCurrentUser()
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    self?.isLoggedIn = false
 | 
					                    self?.isLoggedIn = false
 | 
				
			||||||
                    self?.errorMessage = error ?? NSLocalizedString("Произошла ошибка.", comment: "")
 | 
					                    self?.errorMessage = error ?? NSLocalizedString("Произошла ошибка.", comment: "")
 | 
				
			||||||
                    self?.showError = false
 | 
					                    self?.showError = false
 | 
				
			||||||
 | 
					                    self?.socketService.disconnect()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                self?.isLoading = false
 | 
					                self?.isLoading = false
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -59,9 +62,11 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
                if success {
 | 
					                if success {
 | 
				
			||||||
                    self?.loadStoredUser()
 | 
					                    self?.loadStoredUser()
 | 
				
			||||||
                    self?.isLoggedIn = true
 | 
					                    self?.isLoggedIn = true
 | 
				
			||||||
 | 
					                    self?.socketService.connectForCurrentUser()
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    self?.errorMessage = error ?? NSLocalizedString("Неизвестная ошибка", comment: "")
 | 
					                    self?.errorMessage = error ?? NSLocalizedString("Неизвестная ошибка", comment: "")
 | 
				
			||||||
                    self?.showError = true
 | 
					                    self?.showError = true
 | 
				
			||||||
 | 
					                    self?.socketService.disconnect()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -73,6 +78,9 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
                if success {
 | 
					                if success {
 | 
				
			||||||
                    self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина
 | 
					                    self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина
 | 
				
			||||||
                    self?.loadStoredUser()
 | 
					                    self?.loadStoredUser()
 | 
				
			||||||
 | 
					                    self?.socketService.connectForCurrentUser()
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    self?.socketService.disconnect()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                completion(success, message)
 | 
					                completion(success, message)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -87,6 +95,7 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
                    self?.password = ""
 | 
					                    self?.password = ""
 | 
				
			||||||
                    self?.isLoggedIn = true
 | 
					                    self?.isLoggedIn = true
 | 
				
			||||||
                    self?.showError = false
 | 
					                    self?.showError = false
 | 
				
			||||||
 | 
					                    self?.socketService.connectForCurrentUser()
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    self?.username = ""
 | 
					                    self?.username = ""
 | 
				
			||||||
                    self?.userId = ""
 | 
					                    self?.userId = ""
 | 
				
			||||||
@ -94,6 +103,7 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
                    self?.isLoggedIn = false
 | 
					                    self?.isLoggedIn = false
 | 
				
			||||||
                    self?.errorMessage = error ?? NSLocalizedString("Ошибка при деавторизации.", comment: "")
 | 
					                    self?.errorMessage = error ?? NSLocalizedString("Ошибка при деавторизации.", comment: "")
 | 
				
			||||||
                    self?.showError = false
 | 
					                    self?.showError = false
 | 
				
			||||||
 | 
					                    self?.socketService.disconnect()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ struct AppConfig {
 | 
				
			|||||||
    //static let SERVICE = Bundle.main.bundleIdentifier ?? "default.service"
 | 
					    //static let SERVICE = Bundle.main.bundleIdentifier ?? "default.service"
 | 
				
			||||||
    static let PROTOCOL = "https"
 | 
					    static let PROTOCOL = "https"
 | 
				
			||||||
    static let API_SERVER = "\(PROTOCOL)://api.yobble.org"
 | 
					    static let API_SERVER = "\(PROTOCOL)://api.yobble.org"
 | 
				
			||||||
 | 
					    static let SOCKET_PATH = "/socket.io/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static let USER_AGENT = "yobble ios"
 | 
					    static let USER_AGENT = "yobble ios"
 | 
				
			||||||
    static let APP_BUILD = "appstore" // appstore / freestore
 | 
					    static let APP_BUILD = "appstore" // appstore / freestore
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user