Compare commits

..

No commits in common. "4a2636879d9dacf5354632174fd8ed37b0c3218a" and "24f8ef9c55bdc23edc5d85ef10efbaaf77671963" have entirely different histories.

9 changed files with 0 additions and 372 deletions

View File

@ -1,159 +0,0 @@
<!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>

View File

@ -6,10 +6,6 @@
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
1A85C6CC2EA6FD73009FA847 /* SocketIO in Frameworks */ = {isa = PBXBuildFile; productRef = 1A85C6CB2EA6FD73009FA847 /* SocketIO */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
1A6D61DB2E7CD04000B9F736 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
@ -56,7 +52,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1A85C6CC2EA6FD73009FA847 /* SocketIO in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -117,7 +112,6 @@
);
name = yobble;
packageProductDependencies = (
1A85C6CB2EA6FD73009FA847 /* SocketIO */,
);
productName = yobble;
productReference = 1A6D61CC2E7CD03E00B9F736 /* yobble.app */;
@ -202,9 +196,6 @@
);
mainGroup = 1A6D61C32E7CD03E00B9F736;
minimizedProjectReferenceProxies = 1;
packageReferences = (
1A85C6CA2EA6FC08009FA847 /* XCRemoteSwiftPackageReference "socket" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 1A6D61CD2E7CD03E00B9F736 /* Products */;
projectDirPath = "";
@ -607,25 +598,6 @@
defaultConfigurationName = Release;
};
/* 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 */;
}

View File

@ -1,24 +0,0 @@
{
"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
}

View File

@ -1,5 +0,0 @@
import Foundation
extension Notification.Name {
static let accessTokenDidChange = Notification.Name("accessTokenDidChange")
}

View File

@ -69,8 +69,6 @@ final class AuthService {
}
UserDefaults.standard.set(username, forKey: "currentUser")
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
completion(true, nil)
} catch {
completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
@ -177,8 +175,6 @@ final class AuthService {
UserDefaults.standard.removeObject(forKey: "currentUser")
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
let allUsers = KeychainService.shared.getAllServices()
for user in allUsers {
let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil
@ -187,7 +183,6 @@ final class AuthService {
if hasAccessToken && hasRefreshToken {
UserDefaults.standard.set(user, forKey: "currentUser")
if AppConfig.DEBUG { print("Logout: переключились на пользователя \(user)") }
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
completion(true, nil)
return
}

View File

@ -266,8 +266,6 @@ final class NetworkClient {
KeychainService.shared.save(userId, forKey: "userId", service: tokenInfo.login)
}
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
self.completeRefresh(success: true)
} catch {
self.completeRefresh(success: false)

View File

@ -1,138 +0,0 @@
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)",
"User-Agent": AppConfig.USER_AGENT
]),
.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
}

View File

@ -18,7 +18,6 @@ class LoginViewModel: ObservableObject {
@Published var isLoggedIn: Bool = false
private let authService = AuthService()
private let socketService = SocketService.shared
private enum DefaultsKeys {
static let currentUser = "currentUser"
@ -39,12 +38,10 @@ class LoginViewModel: ObservableObject {
if success {
self?.loadStoredUser()
self?.isLoggedIn = true
self?.socketService.connectForCurrentUser()
} else {
self?.isLoggedIn = false
self?.errorMessage = error ?? NSLocalizedString("Произошла ошибка.", comment: "")
self?.showError = false
self?.socketService.disconnect()
}
self?.isLoading = false
}
@ -62,11 +59,9 @@ class LoginViewModel: ObservableObject {
if success {
self?.loadStoredUser()
self?.isLoggedIn = true
self?.socketService.connectForCurrentUser()
} else {
self?.errorMessage = error ?? NSLocalizedString("Неизвестная ошибка", comment: "")
self?.showError = true
self?.socketService.disconnect()
}
}
}
@ -78,9 +73,6 @@ class LoginViewModel: ObservableObject {
if success {
self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина
self?.loadStoredUser()
self?.socketService.connectForCurrentUser()
} else {
self?.socketService.disconnect()
}
completion(success, message)
}
@ -95,7 +87,6 @@ class LoginViewModel: ObservableObject {
self?.password = ""
self?.isLoggedIn = true
self?.showError = false
self?.socketService.connectForCurrentUser()
} else {
self?.username = ""
self?.userId = ""
@ -103,7 +94,6 @@ class LoginViewModel: ObservableObject {
self?.isLoggedIn = false
self?.errorMessage = error ?? NSLocalizedString("Ошибка при деавторизации.", comment: "")
self?.showError = false
self?.socketService.disconnect()
}
}
}

View File

@ -5,7 +5,6 @@ struct AppConfig {
//static let SERVICE = Bundle.main.bundleIdentifier ?? "default.service"
static let PROTOCOL = "https"
static let API_SERVER = "\(PROTOCOL)://api.yobble.org"
static let SOCKET_PATH = "/socket.io/"
static let USER_AGENT = "yobble ios"
static let APP_BUILD = "appstore" // appstore / freestore